diff options
1316 files changed, 41743 insertions, 11719 deletions
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb index e89809138548..e8e7ec901785 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "9653376" + build_id: "11947186" target: "CtsShim" source_file: "aosp_riscv64/CtsShimPriv.apk" } @@ -8,7 +8,7 @@ drops { version: "" version_group: "" git_project: "platform/frameworks/base" - git_branch: "master" + git_branch: "main" transform: TRANSFORM_NONE transform_options { } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb index 04092366c257..6113b6a6dc8f 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "9653376" + build_id: "11947186" target: "CtsShim" source_file: "aosp_riscv64/CtsShim.apk" } @@ -8,7 +8,7 @@ drops { version: "" version_group: "" git_project: "platform/frameworks/base" - git_branch: "master" + git_branch: "main" transform: TRANSFORM_NONE transform_options { } diff --git a/Ravenwood.bp b/Ravenwood.bp index 11da20aa6e02..159c17e13106 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -215,111 +215,37 @@ genrule { java_library { name: "services.core.ravenwood-jarjar", + defaults: ["ravenwood-internal-only-visibility-java"], installable: false, static_libs: [ "services.core.ravenwood", ], jarjar_rules: ":ravenwood-services-jarjar-rules", - visibility: ["//visibility:private"], -} - -java_library { - name: "services.fakes.ravenwood-jarjar", - installable: false, - srcs: [":services.fakes-sources"], - libs: [ - "ravenwood-framework", - "services.core.ravenwood", - ], - jarjar_rules: ":ravenwood-services-jarjar-rules", - visibility: ["//visibility:private"], -} - -java_library { - name: "mockito-ravenwood-prebuilt", - installable: false, - static_libs: [ - "mockito-robolectric-prebuilt", - ], -} - -java_library { - name: "inline-mockito-ravenwood-prebuilt", - installable: false, - static_libs: [ - "inline-mockito-robolectric-prebuilt", - ], } // Jars in "ravenwood-runtime" are set to the classpath, sorted alphabetically. // Rename some of the dependencies to make sure they're included in the intended order. java_genrule { name: "100-framework-minus-apex.ravenwood", + defaults: ["ravenwood-internal-only-visibility-genrule"], cmd: "cp $(in) $(out)", srcs: [":framework-minus-apex.ravenwood"], out: ["100-framework-minus-apex.ravenwood.jar"], - visibility: ["//visibility:private"], } java_genrule { // Use 200 to make sure it comes before the mainline stub ("all-updatable..."). name: "200-kxml2-android", + defaults: ["ravenwood-internal-only-visibility-genrule"], cmd: "cp $(in) $(out)", srcs: [":kxml2-android"], out: ["200-kxml2-android.jar"], - visibility: ["//visibility:private"], } java_genrule { name: "z00-all-updatable-modules-system-stubs", + defaults: ["ravenwood-internal-only-visibility-genrule"], cmd: "cp $(in) $(out)", srcs: [":all-updatable-modules-system-stubs"], out: ["z00-all-updatable-modules-system-stubs.jar"], - visibility: ["//visibility:private"], -} - -android_ravenwood_libgroup { - name: "ravenwood-runtime", - libs: [ - "100-framework-minus-apex.ravenwood", - "200-kxml2-android", - - "ravenwood-runtime-common-ravenwood", - - "android.test.mock.ravenwood", - "ravenwood-helper-runtime", - "hoststubgen-helper-runtime.ravenwood", - "services.core.ravenwood-jarjar", - "services.fakes.ravenwood-jarjar", - - // Provide runtime versions of utils linked in below - "junit", - "truth", - "flag-junit", - "ravenwood-framework", - "ravenwood-junit-impl", - "ravenwood-junit-impl-flag", - "mockito-ravenwood-prebuilt", - "inline-mockito-ravenwood-prebuilt", - - // It's a stub, so it should be towards the end. - "z00-all-updatable-modules-system-stubs", - ], - jni_libs: [ - "libandroid_runtime", - "libravenwood_runtime", - ], -} - -android_ravenwood_libgroup { - name: "ravenwood-utils", - libs: [ - "junit", - "truth", - "flag-junit", - "ravenwood-framework", - "ravenwood-junit", - "mockito-ravenwood-prebuilt", - "inline-mockito-ravenwood-prebuilt", - ], } diff --git a/SQLITE_OWNERS b/SQLITE_OWNERS index 1ff72e7689a7..783a0b6f6212 100644 --- a/SQLITE_OWNERS +++ b/SQLITE_OWNERS @@ -1,2 +1,9 @@ +# Android platform SQLite owners are responsible for: +# 1. Periodically updating libsqlite from upstream sqlite.org. +# 2. Escalating libsqlite bug reports to upstream sqlite.org. +# 3. Addressing bugs, performance regressions, and feature requests +# in Android SDK SQLite wrappers (android.database.sqlite.*). +# 4. Reviewing proposed changes to said Android SDK SQLite wrappers. + shayba@google.com shombert@google.com diff --git a/apct-tests/perftests/core/src/android/permission/AppOpsPerfTest.kt b/apct-tests/perftests/core/src/android/permission/AppOpsPerfTest.kt deleted file mode 100644 index daf991c313c8..000000000000 --- a/apct-tests/perftests/core/src/android/permission/AppOpsPerfTest.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.permission - -import android.app.AppOpsManager -import android.content.Context -import android.perftests.utils.PerfStatusReporter -import androidx.test.core.app.ApplicationProvider -import androidx.test.filters.LargeTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -@LargeTest -/** - * Performance unit tests for app ops APIs. - * - * The APIs under test are used for checking permissions and tracking permission accesses and are - * therefore invoked frequently by the system for all permission-protected data accesses, hence - * these APIs should be monitored closely for performance. - */ -class AppOpsPerfTest { - @get:Rule val perfStatusReporter = PerfStatusReporter() - private lateinit var appOpsManager: AppOpsManager - private lateinit var opPackageName: String - private var opPackageUid: Int = 0 - - @Before - fun setUp() { - val context: Context = ApplicationProvider.getApplicationContext() - appOpsManager = context.getSystemService<AppOpsManager>(AppOpsManager::class.java)!! - opPackageName = context.getOpPackageName() - opPackageUid = context.getPackageManager().getPackageUid(opPackageName, 0) - } - - @Test - fun testNoteOp() { - val state = perfStatusReporter.benchmarkState - while (state.keepRunning()) { - appOpsManager.noteOp( - AppOpsManager.OPSTR_FINE_LOCATION, - opPackageUid, - opPackageName, - null, - null - ) - } - } - - @Test - fun testUnsafeCheckOp() { - val state = perfStatusReporter.benchmarkState - while (state.keepRunning()) { - appOpsManager.unsafeCheckOp( - AppOpsManager.OPSTR_FINE_LOCATION, - opPackageUid, - opPackageName - ) - } - } -} diff --git a/apct-tests/perftests/core/src/android/permission/OWNERS b/apct-tests/perftests/core/src/android/permission/OWNERS deleted file mode 100644 index b4b2b9e30e90..000000000000 --- a/apct-tests/perftests/core/src/android/permission/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 137825 - -include platform/frameworks/base:/core/java/android/permission/OWNERS
\ No newline at end of file diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index d80d3c8d9301..b955032ff8e4 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -18,11 +18,8 @@ package android.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.window.flags.Flags.windowSessionRelayoutInfo; - import android.app.Activity; import android.content.Context; -import android.os.Bundle; import android.os.RemoteException; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; @@ -131,7 +128,6 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration(); final InsetsState mOutInsetsState = new InsetsState(); final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array(); - final Bundle mOutBundle = windowSessionRelayoutInfo() ? null : new Bundle(); final WindowRelayoutResult mOutRelayoutResult; final IWindow mWindow; final View mView; @@ -152,26 +148,16 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase mHeight = mView.getMeasuredHeight(); mOutSurfaceControl = mView.getViewRootImpl().getSurfaceControl(); mViewVisibility = visibilitySupplier; - mOutRelayoutResult = windowSessionRelayoutInfo() - ? new WindowRelayoutResult(mOutFrames, mOutMergedConfiguration, - mOutSurfaceControl, mOutInsetsState, mOutControls) - : null; + mOutRelayoutResult = new WindowRelayoutResult(mOutFrames, mOutMergedConfiguration, + mOutSurfaceControl, mOutInsetsState, mOutControls); } void runBenchmark(BenchmarkState state) throws RemoteException { final IWindowSession session = WindowManagerGlobal.getWindowSession(); while (state.keepRunning()) { mRelayoutSeq++; - if (windowSessionRelayoutInfo()) { - session.relayout(mWindow, mParams, mWidth, mHeight, - mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */, - mOutRelayoutResult); - } else { - session.relayoutLegacy(mWindow, mParams, mWidth, mHeight, - mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */, - mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, - mOutInsetsState, mOutControls, mOutBundle); - } + session.relayout(mWindow, mParams, mWidth, mHeight, mViewVisibility.getAsInt(), + mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */, mOutRelayoutResult); } } } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index b982d1253e21..dfa72069c28a 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -4925,7 +4925,6 @@ public class AlarmManagerService extends SystemService { sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); sdFilter.addAction(Intent.ACTION_USER_STOPPED); if (mStartUserBeforeScheduledAlarms) { - sdFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED); sdFilter.addAction(Intent.ACTION_USER_REMOVED); } sdFilter.addAction(Intent.ACTION_UID_REMOVED); @@ -4958,14 +4957,6 @@ public class AlarmManagerService extends SystemService { mTemporaryQuotaReserve.removeForUser(userHandle); } return; - case Intent.ACTION_LOCKED_BOOT_COMPLETED: - final int handle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (handle >= 0) { - if (mStartUserBeforeScheduledAlarms) { - mUserWakeupStore.onUserStarted(handle); - } - } - return; case Intent.ACTION_USER_REMOVED: final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (user >= 0) { diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java index 7fc630c964e5..dc5e3414a819 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java @@ -98,12 +98,7 @@ public class UserWakeupStore { */ @GuardedBy("mUserWakeupLock") private final SparseLongArray mUserStarts = new SparseLongArray(); - /** - * A list of users that are in a phase after they have been started but before alarms were - * initialized. - */ - @GuardedBy("mUserWakeupLock") - private final SparseLongArray mStartingUsers = new SparseLongArray(); + private Executor mBackgroundExecutor; private static final File USER_WAKEUP_DIR = new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME); @@ -124,9 +119,6 @@ public class UserWakeupStore { */ public void addUserWakeup(int userId, long alarmTime) { synchronized (mUserWakeupLock) { - // This should not be needed, but if an app in the user is scheduling an alarm clock, we - // consider the user start complete. There is a dedicated removal when user is started. - mStartingUsers.delete(userId); mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset()); } updateUserListFile(); @@ -192,23 +184,10 @@ public class UserWakeupStore { } /** - * Move user from wakeup list to starting user list. + * Remove scheduled user wakeup from the list when it is started. */ public void onUserStarting(int userId) { - synchronized (mUserWakeupLock) { - final long wakeup = getWakeupTimeForUser(userId); - if (wakeup >= 0) { - mStartingUsers.put(userId, wakeup); - mUserStarts.delete(userId); - } - } - } - - /** - * Remove userId from starting user list once start is complete. - */ - public void onUserStarted(int userId) { - if (deleteWakeupFromStartingUsers(userId)) { + if (deleteWakeupFromUserStarts(userId)) { updateUserListFile(); } } @@ -217,7 +196,7 @@ public class UserWakeupStore { * Remove userId from the store when the user is removed. */ public void onUserRemoved(int userId) { - if (deleteWakeupFromUserStarts(userId) || deleteWakeupFromStartingUsers(userId)) { + if (deleteWakeupFromUserStarts(userId)) { updateUserListFile(); } } @@ -227,29 +206,14 @@ public class UserWakeupStore { * @return true if an entry is found and the list of wakeups changes. */ private boolean deleteWakeupFromUserStarts(int userId) { - int index; synchronized (mUserWakeupLock) { - index = mUserStarts.indexOfKey(userId); + final int index = mUserStarts.indexOfKey(userId); if (index >= 0) { mUserStarts.removeAt(index); + return true; } + return false; } - return index >= 0; - } - - /** - * Remove wakeup for a given userId from mStartingUsers. - * @return true if an entry is found and the list of wakeups changes. - */ - private boolean deleteWakeupFromStartingUsers(int userId) { - int index; - synchronized (mUserWakeupLock) { - index = mStartingUsers.indexOfKey(userId); - if (index >= 0) { - mStartingUsers.removeAt(index); - } - } - return index >= 0; } /** @@ -299,9 +263,6 @@ public class UserWakeupStore { for (int i = 0; i < mUserStarts.size(); i++) { listOfUsers.add(new Pair<>(mUserStarts.keyAt(i), mUserStarts.valueAt(i))); } - for (int i = 0; i < mStartingUsers.size(); i++) { - listOfUsers.add(new Pair<>(mStartingUsers.keyAt(i), mStartingUsers.valueAt(i))); - } } Collections.sort(listOfUsers, Comparator.comparingLong(pair -> pair.second)); for (int i = 0; i < listOfUsers.size(); i++) { @@ -329,7 +290,6 @@ public class UserWakeupStore { } synchronized (mUserWakeupLock) { mUserStarts.clear(); - mStartingUsers.clear(); } try (FileInputStream fis = userWakeupFile.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(fis); @@ -396,14 +356,6 @@ public class UserWakeupStore { TimeUtils.formatDuration(mUserStarts.valueAt(i), nowELAPSED, pw); pw.println(); } - pw.println(mStartingUsers.size() + " starting users: "); - for (int i = 0; i < mStartingUsers.size(); i++) { - pw.print("UserId: "); - pw.print(mStartingUsers.keyAt(i)); - pw.print(", userStartTime: "); - TimeUtils.formatDuration(mStartingUsers.valueAt(i), nowELAPSED, pw); - pw.println(); - } pw.decreaseIndent(); } } diff --git a/core/api/current.txt b/core/api/current.txt index 245d12df8eca..69ead8f85623 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -2456,7 +2456,7 @@ package android { field public static final int config_longAnimTime = 17694722; // 0x10e0002 field public static final int config_mediumAnimTime = 17694721; // 0x10e0001 field public static final int config_shortAnimTime = 17694720; // 0x10e0000 - field public static final int status_bar_notification_info_maxnum = 17694723; // 0x10e0003 + field @Deprecated public static final int status_bar_notification_info_maxnum = 17694723; // 0x10e0003 } public static final class R.interpolator { @@ -2550,7 +2550,7 @@ package android { field public static final int search_go = 17039372; // 0x104000c field public static final int selectAll = 17039373; // 0x104000d field public static final int selectTextMode = 17039382; // 0x1040016 - field public static final int status_bar_notification_info_overflow = 17039383; // 0x1040017 + field @Deprecated public static final int status_bar_notification_info_overflow = 17039383; // 0x1040017 field public static final int unknownName = 17039374; // 0x104000e field public static final int untitled = 17039375; // 0x104000f field @Deprecated public static final int yes = 17039379; // 0x1040013 @@ -6701,7 +6701,7 @@ package android.app { method @NonNull public android.app.Notification.Builder setExtras(android.os.Bundle); method @NonNull public android.app.Notification.Builder setFlag(int, boolean); method @NonNull public android.app.Notification.Builder setForegroundServiceBehavior(int); - method @NonNull public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean); + method @NonNull @RequiresPermission(android.Manifest.permission.USE_FULL_SCREEN_INTENT) public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean); method @NonNull public android.app.Notification.Builder setGroup(String); method @NonNull public android.app.Notification.Builder setGroupAlertBehavior(int); method @NonNull public android.app.Notification.Builder setGroupSummary(boolean); @@ -9826,6 +9826,7 @@ package android.companion { method public void onAssociationPending(@NonNull android.content.IntentSender); method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender); method public abstract void onFailure(@Nullable CharSequence); + method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int); } public abstract class CompanionDeviceService extends android.app.Service { @@ -55311,7 +55312,7 @@ package android.view.accessibility { field public static final int TYPE_MAGNIFICATION_OVERLAY = 6; // 0x6 field public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; // 0x5 field public static final int TYPE_SYSTEM = 3; // 0x3 - field @FlaggedApi("android.view.accessibility.add_type_window_control") public static final int TYPE_WINDOW_CONTROL = 7; // 0x7 + field @FlaggedApi("android.view.accessibility.enable_type_window_control") public static final int TYPE_WINDOW_CONTROL = 7; // 0x7 } public class CaptioningManager { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 50d97cf0626f..9e2872f8f6cc 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11471,6 +11471,19 @@ package android.os.storage { } +package android.os.vibrator.persistence { + + @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration { + method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator); + } + + @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class VibrationXmlParser { + method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.vibrator.persistence.ParsedVibration parse(@NonNull java.io.InputStream) throws java.io.IOException; + method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.InputStream) throws java.io.IOException; + } + +} + package android.permission { public final class AdminPermissionControlParams implements android.os.Parcelable { @@ -12886,7 +12899,14 @@ package android.service.notification { field public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content"; field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; field public static final String KEY_TEXT_REPLIES = "key_text_replies"; + field @FlaggedApi("android.service.notification.notification_classification") public static final String KEY_TYPE = "key_type"; field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; + field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_CONTENT_RECOMMENDATION = 4; // 0x4 + field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_NEWS = 3; // 0x3 + field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_OTHER = 0; // 0x0 + field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_PROMOTION = 1; // 0x1 + field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_SOCIAL_MEDIA = 2; // 0x2 + field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_UNKNOWN = -1; // 0xffffffff } public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index c2f960f85c73..1352465b892d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -379,6 +379,10 @@ package android.app { method public void setImportantConversation(boolean); method public void setOriginalImportance(int); method public void setUserVisibleTaskShown(boolean); + field @FlaggedApi("android.service.notification.notification_classification") public static final String NEWS_ID = "android.app.news"; + field @FlaggedApi("android.service.notification.notification_classification") public static final String PROMOTIONS_ID = "android.app.promotions"; + field @FlaggedApi("android.service.notification.notification_classification") public static final String RECS_ID = "android.app.recs"; + field @FlaggedApi("android.service.notification.notification_classification") public static final String SOCIAL_MEDIA_ID = "android.app.social"; } public final class NotificationChannelGroup implements android.os.Parcelable { @@ -2757,21 +2761,24 @@ package android.os.vibrator { package android.os.vibrator.persistence { - public class ParsedVibration { - method @NonNull public java.util.List<android.os.VibrationEffect> getVibrationEffects(); - method @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator); + @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration { + ctor public ParsedVibration(@NonNull java.util.List<android.os.VibrationEffect>); + method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator); } - public final class VibrationXmlParser { - method @Nullable public static android.os.vibrator.persistence.ParsedVibration parseDocument(@NonNull java.io.Reader) throws java.io.IOException; - method @Nullable public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.Reader) throws java.io.IOException; + @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class VibrationXmlParser { + method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.vibrator.persistence.ParsedVibration parse(@NonNull java.io.InputStream) throws java.io.IOException; + method @FlaggedApi("android.os.vibrator.vibration_xml_apis") @NonNull public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.InputStream) throws java.io.IOException; + } + + public static final class VibrationXmlParser.ParseFailedException extends java.io.IOException { } public final class VibrationXmlSerializer { - method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException, android.os.vibrator.persistence.VibrationXmlSerializer.SerializationFailedException; + method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException; } - public static final class VibrationXmlSerializer.SerializationFailedException extends java.lang.RuntimeException { + public static final class VibrationXmlSerializer.SerializationFailedException extends java.io.IOException { } } diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java index f2debfcfa6b1..4bfa3b340ec9 100644 --- a/core/java/android/app/AppCompatCallbacks.java +++ b/core/java/android/app/AppCompatCallbacks.java @@ -82,7 +82,7 @@ public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDel private void reportChange(long changeId, int state, boolean isLoggable) { int uid = Process.myUid(); - mChangeReporter.reportChange(uid, changeId, state, isLoggable); + mChangeReporter.reportChange(uid, changeId, state, false, isLoggable); } } diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 19253801b678..62b541261f34 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -397,6 +397,23 @@ public final class AutomaticZenRule implements Parcelable { } /** + * Sets the component name of the + * {@link android.service.notification.ConditionProviderService} that manages this rule + * (but note that {@link android.service.notification.ConditionProviderService} is + * deprecated in favor of using {@link NotificationManager#setAutomaticZenRuleState} to + * notify the system about the state of your rule). + * + * <p>This is exclusive with {@link #setConfigurationActivity}; rules where a configuration + * activity is set will not use the component set here to determine whether the rule + * should be active. + * + * @hide + */ + public void setOwner(@Nullable ComponentName owner) { + this.owner = owner; + } + + /** * Sets the configuration activity - an activity that handles * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information * about this rule and/or allows them to configure it. This is required to be non-null for rules diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 3b9a5d3b2cf2..baed4fd02e5b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2303,19 +2303,26 @@ class ContextImpl extends Context { && PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) { VirtualDeviceManager virtualDeviceManager = getSystemService(VirtualDeviceManager.class); - VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId); - if (virtualDevice != null) { - if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO) - && !virtualDevice.hasCustomAudioInputSupport()) - || (Objects.equals(permission, Manifest.permission.CAMERA) - && !virtualDevice.hasCustomCameraSupport())) { - deviceId = Context.DEVICE_ID_DEFAULT; - } - } else { + if (virtualDeviceManager == null) { Slog.e( TAG, - "virtualDevice is not found when device id is not default. deviceId = " + "VDM is not enabled when device id is not default. deviceId = " + deviceId); + } else { + VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId); + if (virtualDevice != null) { + if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO) + && !virtualDevice.hasCustomAudioInputSupport()) + || (Objects.equals(permission, Manifest.permission.CAMERA) + && !virtualDevice.hasCustomCameraSupport())) { + deviceId = Context.DEVICE_ID_DEFAULT; + } + } else { + Slog.e( + TAG, + "virtualDevice is not found when device id is not default. deviceId = " + + deviceId); + } } } @@ -3169,6 +3176,11 @@ class ContextImpl extends Context { public void updateDeviceId(int updatedDeviceId) { if (updatedDeviceId != Context.DEVICE_ID_DEFAULT) { VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class); + if (vdm == null) { + throw new IllegalArgumentException( + "VDM is not enabled when updating to non-default device id: " + + updatedDeviceId); + } if (!vdm.isValidVirtualDeviceId(updatedDeviceId)) { throw new IllegalArgumentException( "Not a valid ID of the default device or any virtual device: " diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index ffb920b907ab..15b13dc97554 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -757,15 +757,6 @@ interface IActivityManager { void addStartInfoTimestamp(int key, long timestampNs, int userId); /** - * Reports view related timestamps to be added to the calling apps most - * recent {@link ApplicationStartInfo}. - * - * @param renderThreadDrawStartTimeNs Clock monotonic time in nanoseconds of RenderThread draw start - * @param framePresentedTimeNs Clock monotonic time in nanoseconds of frame presented - */ - oneway void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, long framePresentedTimeNs); - - /** * Return a list of {@link ApplicationExitInfo} records. * * <p class="note"> Note: System stores these historical information in a ring buffer, older diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index a1fa4047f839..aea15e13e5d6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5058,6 +5058,7 @@ public class Notification implements Parcelable * @see Notification#fullScreenIntent */ @NonNull + @RequiresPermission(android.Manifest.permission.USE_FULL_SCREEN_INTENT) public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { mN.fullScreenIntent = intent; setFlag(FLAG_HIGH_PRIORITY, highPriority); @@ -6050,6 +6051,7 @@ public class Notification implements Parcelable bindProfileBadge(contentView, p); bindAlertedIcon(contentView, p); bindExpandButton(contentView, p); + bindCloseButton(contentView, p); mN.mUsesStandardHeader = true; } @@ -6071,6 +6073,15 @@ public class Notification implements Parcelable contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); } + private void bindCloseButton(RemoteViews contentView, StandardTemplateParams p) { + // set default colors + int bgColor = getBackgroundColor(p); + int backgroundColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor); + int foregroundColor = Colors.flattenAlpha(getPrimaryTextColor(p), backgroundColor); + contentView.setInt(R.id.close_button, "setForegroundColor", foregroundColor); + contentView.setInt(R.id.close_button, "setBackgroundColor", backgroundColor); + } + private void bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft) { if (!p.mHideTime && showsTimeOrChronometer()) { @@ -6222,6 +6233,7 @@ public class Notification implements Parcelable if (appIconRes != 0) { mN.mAppIcon = Icon.createWithResource(mContext, appIconRes); contentView.setImageViewIcon(R.id.icon, mN.mAppIcon); + contentView.setBoolean(R.id.icon, "setShouldShowAppIcon", true); usingAppIcon = true; } else { Log.w(TAG, "bindSmallIcon: could not get the app icon"); diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 3f6c81b4f53a..326d7ceeb2f8 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -72,6 +72,35 @@ public final class NotificationChannel implements Parcelable { public static final String DEFAULT_CHANNEL_ID = "miscellaneous"; /** + * A reserved id for a system channel reserved for promotional notifications. + * @hide + */ + @TestApi + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final String PROMOTIONS_ID = "android.app.promotions"; + /** + * A reserved id for a system channel reserved for non-conversation social media notifications. + * @hide + */ + @TestApi + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final String SOCIAL_MEDIA_ID = "android.app.social"; + /** + * A reserved id for a system channel reserved for news notifications. + * @hide + */ + @TestApi + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final String NEWS_ID = "android.app.news"; + /** + * A reserved id for a system channel reserved for content recommendation notifications. + * @hide + */ + @TestApi + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final String RECS_ID = "android.app.recs"; + + /** * The formatter used by the system to create an id for notification * channels when it automatically creates conversation channels on behalf of an app. The format * string takes two arguments, in this order: the diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 370aff8e7a0f..bb7f893cbbc1 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -1745,10 +1745,15 @@ public class ResourcesManager { if (r.getImpl() != null) { final ResourcesImpl oldImpl = r.getImpl(); // ResourcesImpl constructor will help to append shared library asset paths. - final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(), - oldImpl.getMetrics(), oldImpl.getConfiguration(), - oldImpl.getDisplayAdjustments()); - r.setImpl(newImpl); + if (oldImpl.getAssets().isUpToDate()) { + final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(), + oldImpl.getMetrics(), oldImpl.getConfiguration(), + oldImpl.getDisplayAdjustments()); + r.setImpl(newImpl); + } else { + Slog.w(TAG, "Skip appending shared library asset paths for the " + + "Resource as its assets are not up to date."); + } } } } diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 7d5806a294e0..82271129c69e 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -383,3 +383,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "management_mode_policy_metrics" + namespace: "enterprise" + description: "Enabling management mode and password complexity policy metrics collection" + bug: "293091314" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 18cfca686107..4e0379e3dc3a 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -50,3 +50,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "remote_views_proto" + namespace: "app_widgets" + description: "Enable support for persisting RemoteViews previews to Protobuf" + bug: "306546610" +} diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index b4ad1c8fff12..34cfa584b683 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -21,6 +21,8 @@ import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_P import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER; import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH; +import static java.util.Collections.unmodifiableMap; + import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -56,6 +58,7 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.NotificationListenerService; +import android.util.ArrayMap; import android.util.ExceptionUtils; import android.util.Log; import android.util.SparseArray; @@ -75,6 +78,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.BiConsumer; @@ -119,29 +123,31 @@ public final class CompanionDeviceManager { * is created successfully. */ public static final int RESULT_OK = -1; - + //TODO(b/331459560) Need to update the java doc after API cut for W. /** * The result code to propagate back to the user activity, indicates if the association dialog * is implicitly cancelled. * E.g. phone is locked, switch to another app or press outside the dialog. */ public static final int RESULT_CANCELED = 0; - + //TODO(b/331459560) Need to update the java doc after API cut for W. /** * The result code to propagate back to the user activity, indicates the association dialog * is explicitly declined by the users. */ public static final int RESULT_USER_REJECTED = 1; - + //TODO(b/331459560) Need to update the java doc after API cut for W. /** * The result code to propagate back to the user activity, indicates the association * dialog is dismissed if there's no device found after 20 seconds. */ public static final int RESULT_DISCOVERY_TIMEOUT = 2; - + //TODO(b/331459560) Need to update the java doc after API cut for W. /** * The result code to propagate back to the user activity, indicates the internal error * in CompanionDeviceManager. + * E.g. Missing necessary permissions or duplicate {@link AssociationRequest}s when create the + * {@link AssociationInfo}. */ public static final int RESULT_INTERNAL_ERROR = 3; @@ -368,12 +374,22 @@ public final class CompanionDeviceManager { */ public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {} + //TODO(b/331459560): Add deprecated and remove abstract after API cut for W. /** * Invoked if the association could not be created. * * @param error error message. */ public abstract void onFailure(@Nullable CharSequence error); + + /** + * Invoked if the association could not be created. + * + * @param resultCode indicate the particular reason why the association + * could not be created. + */ + @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE) + public void onFailure(@ResultCode int resultCode) {} } private final ICompanionDeviceManager mService; @@ -1803,8 +1819,12 @@ public final class CompanionDeviceManager { } @Override - public void onFailure(CharSequence error) throws RemoteException { - execute(mCallback::onFailure, error); + public void onFailure(@ResultCode int resultCode) { + if (Flags.associationFailureCode()) { + execute(mCallback::onFailure, resultCode); + } + + execute(mCallback::onFailure, RESULT_CODE_TO_REASON.get(resultCode)); } private <T> void execute(Consumer<T> callback, T arg) { @@ -1988,4 +2008,15 @@ public final class CompanionDeviceManager { } } } + + private static final Map<Integer, String> RESULT_CODE_TO_REASON; + static { + final Map<Integer, String> map = new ArrayMap<>(); + map.put(RESULT_CANCELED, REASON_CANCELED); + map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED); + map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT); + map.put(RESULT_INTERNAL_ERROR, REASON_INTERNAL_ERROR); + + RESULT_CODE_TO_REASON = unmodifiableMap(map); + } } diff --git a/core/java/android/companion/IAssociationRequestCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl index 8cc2a7194e08..b1be30a1288c 100644 --- a/core/java/android/companion/IAssociationRequestCallback.aidl +++ b/core/java/android/companion/IAssociationRequestCallback.aidl @@ -25,5 +25,5 @@ interface IAssociationRequestCallback { oneway void onAssociationCreated(in AssociationInfo associationInfo); - oneway void onFailure(in CharSequence error); + oneway void onFailure(in int resultCode); }
\ No newline at end of file diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig index fd4ba83b02e3..ee9114f74092 100644 --- a/core/java/android/companion/flags.aconfig +++ b/core/java/android/companion/flags.aconfig @@ -53,4 +53,12 @@ flag { namespace: "companion" description: "Unpair with an associated bluetooth device" bug: "322237619" -}
\ No newline at end of file +} + +flag { + name: "association_failure_code" + is_exported: true + namespace: "companion" + description: "Enable association failure code API" + bug: "331459560" +} diff --git a/core/java/android/content/ComponentCallbacks.java b/core/java/android/content/ComponentCallbacks.java index fb9536f846aa..3acb37385de9 100644 --- a/core/java/android/content/ComponentCallbacks.java +++ b/core/java/android/content/ComponentCallbacks.java @@ -58,7 +58,9 @@ public interface ComponentCallbacks { * @deprecated Since API level 14 this is superseded by * {@link ComponentCallbacks2#onTrimMemory}. * Since API level 34 this is never called. - * Apps targeting API level 34 and above may provide an empty implementation. + * If you're overriding ComponentCallbacks2#onTrimMemory and + * your minSdkVersion is greater than API 14, you can provide + * an empty implementation for this method. */ @Deprecated void onLowMemory(); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index dfa2973816bd..a6edab1420ba 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -7630,6 +7630,13 @@ public class Intent implements Parcelable, Cloneable { | FLAG_GRANT_PREFIX_URI_PERMISSION; /** + * Flags that are not normally set by application code, but set for you by the system. + */ + private static final int SYSTEM_ONLY_FLAGS = FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + | FLAG_ACTIVITY_BROUGHT_TO_FRONT + | FLAG_RECEIVER_FROM_SHELL; + + /** * Local flag indicating this instance was created by copy constructor. */ private static final int LOCAL_FLAG_FROM_COPY = 1 << 0; @@ -7682,6 +7689,11 @@ public class Intent implements Parcelable, Cloneable { @TestApi public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1 << 0; + /** + * Extended flags that are not normally set by application code, but set for you by the system. + */ + private static final int SYSTEM_ONLY_EXTENDED_FLAGS = EXTENDED_FLAG_FILTER_MISMATCH; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // toUri() and parseUri() options. @@ -12625,6 +12637,28 @@ public class Intent implements Parcelable, Cloneable { } } + /** + * Prepare this {@link Intent} to enter system_server. + * + * @hide + */ + public void prepareToEnterSystemServer() { + // Refuse possible leaked file descriptors + if (hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + // These flags are set only by the system, and should be stripped out as soon as the intent + // is received by system_server from the caller so it can be properly updated later. + removeFlags(SYSTEM_ONLY_FLAGS); + removeExtendedFlags(SYSTEM_ONLY_EXTENDED_FLAGS); + if (mOriginalIntent != null) { + mOriginalIntent.prepareToEnterSystemServer(); + } + if (mSelector != null) { + mSelector.prepareToEnterSystemServer(); + } + } + /** @hide */ public boolean hasWebURI() { if (getData() == null) { diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS index a37408b7d847..6d9dc456f38d 100644 --- a/core/java/android/content/OWNERS +++ b/core/java/android/content/OWNERS @@ -14,3 +14,4 @@ per-file ComponentCallbacksController = file:/services/core/java/com/android/ser per-file ComponentCallbacksController = charlesccchen@google.com per-file AttributionSource* = file:/core/java/android/permission/OWNERS per-file Broadcast* = file:/BROADCASTS_OWNERS +per-file ComponentCallbacks*.java = file:/PERFORMANCE_OWNERS
\ No newline at end of file diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 273e40a21bb2..c0c1c31a832a 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -32,6 +32,7 @@ import android.content.res.Configuration.NativeConfig; import android.content.res.loader.ResourcesLoader; import android.os.Build; import android.os.ParcelFileDescriptor; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; @@ -448,7 +449,7 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addAssetPath(String path) { - return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/); + return addAssetPathInternal(List.of(path), false, false, false); } /** @@ -458,7 +459,7 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addAssetPathAsSharedLibrary(String path) { - return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/); + return addAssetPathInternal(List.of(path), false, true, false); } /** @@ -468,35 +469,103 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addOverlayPath(String path) { - return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/); + return addAssetPathInternal(List.of(path), true, false, false); } /** * @hide */ - public void addSharedLibraryPaths(@NonNull String[] paths) { - final int length = paths.length; - for (int i = 0; i < length; i++) { - addAssetPathInternal(paths[i], false, true); - } + public void addSharedLibraryPaths(@NonNull List<String> paths) { + addAssetPathInternal(paths, false, true, true); } - private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) { - Objects.requireNonNull(path, "path"); + private int addAssetPathInternal(List<String> paths, boolean overlay, boolean appAsLib, + boolean presetAssets) { + Objects.requireNonNull(paths, "paths"); + if (paths.isEmpty()) { + return 0; + } + synchronized (this) { ensureOpenLocked(); - final int count = mApkAssets.length; - // See if we already have it loaded. - for (int i = 0; i < count; i++) { - if (mApkAssets[i].getAssetPath().equals(path)) { - return i + 1; + // See if we already have some of the paths loaded. + final int originalAssetsCount = mApkAssets.length; + + // Getting an assets' path is a relatively expensive operation, cache them. + final ArrayMap<String, Integer> assetPaths = new ArrayMap<>(originalAssetsCount); + for (int i = 0; i < originalAssetsCount; i++) { + assetPaths.put(mApkAssets[i].getAssetPath(), i); + } + + final ArrayList<String> newPaths = new ArrayList<>(paths.size()); + int lastFoundIndex = -1; + for (int i = 0, pathsSize = paths.size(); i < pathsSize; i++) { + final var path = paths.get(i); + final int index = assetPaths.getOrDefault(path, -1); + if (index < 0) { + newPaths.add(path); + } else { + lastFoundIndex = index; } } + if (newPaths.isEmpty()) { + return lastFoundIndex + 1; + } - final ApkAssets assets; + final var newAssets = loadAssets(newPaths, overlay, appAsLib); + if (newAssets.isEmpty()) { + return 0; + } + mApkAssets = makeNewAssetsArrayLocked(newAssets); + nativeSetApkAssets(mObject, mApkAssets, true, presetAssets); + invalidateCachesLocked(-1); + return originalAssetsCount + 1; + } + } + + /** + * Insert the new assets preserving the correct order: all non-loader assets go before all + * of the loader assets. + */ + @GuardedBy("this") + private @NonNull ApkAssets[] makeNewAssetsArrayLocked( + @NonNull ArrayList<ApkAssets> newNonLoaderAssets) { + final int originalAssetsCount = mApkAssets.length; + int firstLoaderIndex = originalAssetsCount; + for (int i = 0; i < originalAssetsCount; i++) { + if (mApkAssets[i].isForLoader()) { + firstLoaderIndex = i; + break; + } + } + final int newAssetsSize = newNonLoaderAssets.size(); + final var newAssetsArray = new ApkAssets[originalAssetsCount + newAssetsSize]; + if (firstLoaderIndex > 0) { + // This should always be true, but who knows... + System.arraycopy(mApkAssets, 0, newAssetsArray, 0, firstLoaderIndex); + } + for (int i = 0; i < newAssetsSize; i++) { + newAssetsArray[firstLoaderIndex + i] = newNonLoaderAssets.get(i); + } + if (originalAssetsCount > firstLoaderIndex) { + System.arraycopy( + mApkAssets, firstLoaderIndex, + newAssetsArray, firstLoaderIndex + newAssetsSize, + originalAssetsCount - firstLoaderIndex); + } + return newAssetsArray; + } + + private static @NonNull ArrayList<ApkAssets> loadAssets(@NonNull ArrayList<String> paths, + boolean overlay, boolean appAsLib) { + final int pathsSize = paths.size(); + final var loadedAssets = new ArrayList<ApkAssets>(pathsSize); + for (int i = 0; i < pathsSize; i++) { + final var path = paths.get(i); try { - if (overlay) { + final ApkAssets assets; + if (overlay || path.endsWith(".frro")) { // TODO(b/70343104): This hardcoded path will be removed once // addAssetPathInternal is deleted. final String idmapPath = "/data/resource-cache/" @@ -507,16 +576,12 @@ public final class AssetManager implements AutoCloseable { assets = ApkAssets.loadFromPath(path, appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0); } + loadedAssets.add(assets); } catch (IOException e) { - return 0; + Log.w(TAG, "Failed to load asset, path = " + path, e); } - - mApkAssets = Arrays.copyOf(mApkAssets, count + 1); - mApkAssets[count] = assets; - nativeSetApkAssets(mObject, mApkAssets, true, false); - invalidateCachesLocked(-1); - return count + 1; } + return loadedAssets; } /** @hide */ diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 31cacb705d0e..9f8b97418cbf 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -49,6 +49,7 @@ import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.os.Trace; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -71,6 +72,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; @@ -205,13 +207,21 @@ public class ResourcesImpl { @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { mAssets = assets; if (Flags.registerResourcePaths()) { - ArrayMap<String, SharedLibraryAssets> sharedLibMap = + final ArraySet<String> uniquePaths = new ArraySet<>(); + final ArrayList<String> orderedPaths = new ArrayList<>(); + final ArrayMap<String, SharedLibraryAssets> sharedLibMap = ResourcesManager.getInstance().getSharedLibAssetsMap(); final int size = sharedLibMap.size(); for (int i = 0; i < size; i++) { - assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths()); + final var paths = sharedLibMap.valueAt(i).getAllAssetPaths(); + for (int j = 0; j < paths.length; j++) { + if (uniquePaths.add(paths[j])) { + orderedPaths.add(paths[j]); + } + } } - mSharedLibCount = sharedLibMap.size(); + assets.addSharedLibraryPaths(orderedPaths); + mSharedLibCount = size; } mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 32d2a6f803d6..c2424e8a6529 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -347,7 +347,9 @@ public class Camera { VirtualDeviceManager virtualDeviceManager = context.getSystemService(VirtualDeviceManager.class); - return virtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA); + return virtualDeviceManager == null + ? DEVICE_POLICY_DEFAULT + : virtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA); } /** diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index fdd8b04921f5..678bd6bc6336 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -137,6 +137,7 @@ public class BiometricManager { BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE, DEVICE_CREDENTIAL, + MANDATORY_BIOMETRICS, }) @Retention(RetentionPolicy.SOURCE) @interface Types {} @@ -214,6 +215,21 @@ public class BiometricManager { */ int DEVICE_CREDENTIAL = 1 << 15; + /** + * The bit is used to request for mandatory biometrics. + * + * <p> The requirements to trigger mandatory biometrics are as follows: + * 1. User must have enabled the toggle for mandatory biometrics is settings + * 2. User must have enrollments for all {@link #BIOMETRIC_STRONG} sensors available + * 3. The device must not be in a trusted location + * </p> + * + * <p> If all the above conditions are satisfied, only {@link #BIOMETRIC_STRONG} sensors + * will be eligible for authentication, and device credential fallback will be dropped. + * @hide + */ + int MANDATORY_BIOMETRICS = 1 << 16; + } /** diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 37a2df83cc00..42f5fc81fbd8 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -794,10 +794,15 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public void onDialogDismissed(int reason) { // Check the reason and invoke OnClickListener(s) if necessary if (reason == DISMISSED_REASON_NEGATIVE) { - mNegativeButtonInfo.executor.execute(() -> { - mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); - mIsPromptShowing = false; - }); + if (mNegativeButtonInfo != null) { + mNegativeButtonInfo.executor.execute(() -> { + mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); + mIsPromptShowing = false; + }); + } else { + mAuthenticationCallback.onAuthenticationError(BIOMETRIC_ERROR_USER_CANCELED, + null /* errString */); + } } else if (reason == DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS) { if (mContentViewMoreOptionsButtonInfo != null) { mContentViewMoreOptionsButtonInfo.executor.execute(() -> { diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index ba9f30dfcf4b..901f6b7ba5c1 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -195,6 +195,10 @@ public class PromptInfo implements Parcelable { return true; } else if (mContentView != null && isContentViewMoreOptionsButtonUsed()) { return true; + } else if (Flags.mandatoryBiometrics() + && (mAuthenticators & BiometricManager.Authenticators.MANDATORY_BIOMETRICS) + != 0) { + return true; } return false; } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 708f8a173aba..9eb97452444f 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -581,7 +581,9 @@ public final class CameraManager { if (mVirtualDeviceManager == null) { mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class); } - return mVirtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA); + return mVirtualDeviceManager == null + ? DEVICE_POLICY_DEFAULT + : mVirtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA); } // TODO(b/147726300): Investigate how to support foldables/multi-display devices. diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index b2dcf9091198..91caedc0b9a9 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -740,6 +740,12 @@ public abstract class DisplayManagerInternal { */ void onBlockingScreenOn(Runnable unblocker); + /** + * Called while display is turning to screen state other than state ON to notify that any + * pending work from the previous blockScreenOn call should have been cancelled. + */ + void cancelBlockScreenOn(); + /** Whether auto brightness update in doze is allowed */ boolean allowAutoBrightnessInDoze(); } @@ -774,6 +780,12 @@ public abstract class DisplayManagerInternal { boolean blockScreenOn(Runnable unblocker); /** + * Called while display is turning to screen state other than state ON to notify that any + * pending work from the previous blockScreenOn call should have been cancelled. + */ + void cancelBlockScreenOn(); + + /** * Get the brightness levels used to determine automatic brightness based on lux levels. * @param mode The auto-brightness mode * (AutomaticBrightnessController.AutomaticBrightnessMode) diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index f9a2dbb1bdb4..b1b4de35a9c4 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -63,48 +63,53 @@ final class TunerCallbackAdapter extends ITunerCallback.Stub { } void close() { + ProgramList programList; synchronized (mLock) { - if (mProgramList != null) { - mProgramList.close(); + if (mProgramList == null) { + return; } + programList = mProgramList; } + programList.close(); } void setProgramListObserver(@Nullable ProgramList programList, ProgramList.OnCloseListener closeListener) { Objects.requireNonNull(closeListener, "CloseListener cannot be null"); + ProgramList prevProgramList; synchronized (mLock) { - if (mProgramList != null) { - Log.w(TAG, "Previous program list observer wasn't properly closed, closing it..."); - mProgramList.close(); - } + prevProgramList = mProgramList; mProgramList = programList; - if (programList == null) { - return; + } + if (prevProgramList != null) { + Log.w(TAG, "Previous program list observer wasn't properly closed, closing it..."); + prevProgramList.close(); + } + if (programList == null) { + return; + } + programList.setOnCloseListener(() -> { + synchronized (mLock) { + if (mProgramList != programList) { + return; + } + mProgramList = null; + mLastCompleteList = null; } - programList.setOnCloseListener(() -> { - synchronized (mLock) { - if (mProgramList != programList) { - return; - } - mProgramList = null; - mLastCompleteList = null; + closeListener.onClose(); + }); + programList.addOnCompleteListener(() -> { + synchronized (mLock) { + if (mProgramList != programList) { + return; } - closeListener.onClose(); - }); - programList.addOnCompleteListener(() -> { - synchronized (mLock) { - if (mProgramList != programList) { - return; - } - mLastCompleteList = programList.toList(); - if (mDelayedCompleteCallback) { - Log.d(TAG, "Sending delayed onBackgroundScanComplete callback"); - sendBackgroundScanCompleteLocked(); - } + mLastCompleteList = programList.toList(); + if (mDelayedCompleteCallback) { + Log.d(TAG, "Sending delayed onBackgroundScanComplete callback"); + sendBackgroundScanCompleteLocked(); } - }); - } + } + }); } @Nullable List<RadioManager.ProgramInfo> getLastCompleteList() { @@ -245,12 +250,14 @@ final class TunerCallbackAdapter extends ITunerCallback.Stub { @Override public void onProgramListUpdated(ProgramList.Chunk chunk) { mHandler.post(() -> { + ProgramList programList; synchronized (mLock) { if (mProgramList == null) { return; } - mProgramList.apply(Objects.requireNonNull(chunk, "Chunk cannot be null")); + programList = mProgramList; } + programList.apply(Objects.requireNonNull(chunk, "Chunk cannot be null")); }); } diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index 02f3a256aae3..744f6a8d61ac 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -888,6 +888,13 @@ public abstract class BatteryConsumer { return (T) this; } + @SuppressWarnings("unchecked") + @NonNull + public T addConsumedPowerForCustomComponent(int componentId, double componentPower) { + mPowerComponentsBuilder.addConsumedPowerForCustomComponent(componentId, componentPower); + return (T) this; + } + /** * Sets the amount of time used by the specified component, e.g. CPU, WiFi etc. * diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index 164e2659d654..b035f123d5db 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -516,6 +516,19 @@ class PowerComponents { } @NonNull + public Builder addConsumedPowerForCustomComponent(int componentId, double componentPower) { + final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; + if (index < 0 || index >= mData.layout.customPowerComponentCount) { + throw new IllegalArgumentException( + "Unsupported custom power component ID: " + componentId); + } + mData.putDouble(mData.layout.firstCustomConsumedPowerColumn + index, + mData.getDouble(mData.layout.firstCustomConsumedPowerColumn + index) + + componentPower); + return this; + } + + @NonNull public Builder setUsageDurationMillis(BatteryConsumer.Key key, long componentUsageDurationMillis) { mData.putLong(key.mDurationColumnIndex, componentUsageDurationMillis); diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index eda755c05509..c73a422d931b 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -31,3 +31,24 @@ flag { description: "Enables the adaptive haptics feature" bug: "305961689" } + +flag { + namespace: "haptics" + name: "cancel_by_appops" + description: "Cancels ongoing vibrations when the appops mode changes to disallow them" + bug: "230745615" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "haptics" + name: "vibration_xml_apis" + description: "Enabled System APIs for vibration effect XML parser and serializer" + bug: "347273158" + metadata { + purpose: PURPOSE_FEATURE + } +} diff --git a/core/java/android/os/vibrator/persistence/ParsedVibration.java b/core/java/android/os/vibrator/persistence/ParsedVibration.java index a16d21eb87a3..e5543ab6c6f7 100644 --- a/core/java/android/os/vibrator/persistence/ParsedVibration.java +++ b/core/java/android/os/vibrator/persistence/ParsedVibration.java @@ -16,31 +16,35 @@ package android.os.vibrator.persistence; +import static android.os.vibrator.Flags.FLAG_VIBRATION_XML_APIS; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorInfo; -import com.android.internal.annotations.VisibleForTesting; - -import java.util.Collections; import java.util.List; +import java.util.Objects; /** - * The result of parsing a serialized vibration, which can be define by one or more - * {@link VibrationEffect} and a resolution method. + * The result of parsing a serialized vibration. + * + * @see VibrationXmlParser * * @hide */ -@TestApi -@SuppressLint("UnflaggedApi") // @TestApi without associated feature. -public class ParsedVibration { +@TestApi // This was used in CTS before the flag was introduced. +@SystemApi +@FlaggedApi(FLAG_VIBRATION_XML_APIS) +public final class ParsedVibration { private final List<VibrationEffect> mEffects; /** @hide */ + @TestApi public ParsedVibration(@NonNull List<VibrationEffect> effects) { mEffects = effects; } @@ -49,40 +53,28 @@ public class ParsedVibration { public ParsedVibration(@NonNull VibrationEffect effect) { mEffects = List.of(effect); } + /** * Returns the first parsed vibration supported by {@code vibrator}, or {@code null} if none of * the parsed vibrations are supported. * * @hide */ - @TestApi + @TestApi // This was used in CTS before the flag was introduced. + @SystemApi + @FlaggedApi(FLAG_VIBRATION_XML_APIS) @Nullable public VibrationEffect resolve(@NonNull Vibrator vibrator) { return resolve(vibrator.getInfo()); } /** - * Returns the parsed vibrations for testing purposes. - * - * <p>Real callers should not use this method. Instead, they should resolve to a - * {@link VibrationEffect} via {@link #resolve(Vibrator)}. - * - * @hide - */ - @TestApi - @VisibleForTesting - @NonNull - public List<VibrationEffect> getVibrationEffects() { - return Collections.unmodifiableList(mEffects); - } - - /** * Same as {@link #resolve(Vibrator)}, but uses {@link VibratorInfo} instead for resolving. * * @hide */ @Nullable - public final VibrationEffect resolve(@NonNull VibratorInfo info) { + public VibrationEffect resolve(@NonNull VibratorInfo info) { for (int i = 0; i < mEffects.size(); i++) { VibrationEffect effect = mEffects.get(i); if (info.areVibrationFeaturesSupported(effect)) { @@ -91,4 +83,21 @@ public class ParsedVibration { } return null; } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ParsedVibration)) { + return false; + } + ParsedVibration other = (ParsedVibration) o; + return mEffects.equals(other.mEffects); + } + + @Override + public int hashCode() { + return Objects.hashCode(mEffects); + } } diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java index 7202d9a19c30..e2312e0c96c4 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java @@ -16,13 +16,15 @@ package android.os.vibrator.persistence; +import static android.os.vibrator.Flags.FLAG_VIBRATION_XML_APIS; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.VibrationEffect; -import android.util.Slog; import android.util.Xml; import com.android.internal.vibrator.persistence.VibrationEffectXmlParser; @@ -36,9 +38,12 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Reader; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -116,10 +121,10 @@ import java.util.List; * * @hide */ -@TestApi -@SuppressLint("UnflaggedApi") // @TestApi without associated feature. +@TestApi // This was used in CTS before the flag was introduced. +@SystemApi +@FlaggedApi(FLAG_VIBRATION_XML_APIS) public final class VibrationXmlParser { - private static final String TAG = "VibrationXmlParser"; /** * The MIME type for a xml holding a vibration. @@ -168,93 +173,109 @@ public final class VibrationXmlParser { } /** - * Parses XML content from given input stream into a {@link VibrationEffect}. + * Parses XML content from given input stream into a {@link ParsedVibration}. + * + * <p>It supports both the "vibration-effect" and "vibration-select" root tags. + * <ul> + * <li>If "vibration-effect" is the root tag, the serialization provided should contain a + * valid serialization for a single vibration. + * <li>If "vibration-select" is the root tag, the serialization may contain one or more + * valid vibration serializations. + * </ul> + * + * <p>After parsing, it returns a {@link ParsedVibration} that opaquely represents the parsed + * vibration(s), and the caller can get a concrete {@link VibrationEffect} by resolving this + * result to a specific vibrator. + * + * <p>This parser fails with an exception if the content of the input stream does not follow the + * schema or has unsupported values. + * + * @return a {@link ParsedVibration} + * @throws IOException error reading from given {@link InputStream} or parsing the content. + * + * @hide + */ + @TestApi // Replacing test APIs used in CTS before the flagged system APIs was introduced. + @SystemApi + @FlaggedApi(FLAG_VIBRATION_XML_APIS) + @NonNull + public static ParsedVibration parse(@NonNull InputStream inputStream) throws IOException { + return parseDocument(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + } + + /** + * Parses XML content from given input stream into a single {@link VibrationEffect}. * * <p>This method parses an XML content that contains a single, complete {@link VibrationEffect} - * serialization. As such, the root tag must be a "vibration" tag. + * serialization. As such, the root tag must be a "vibration-effect" tag. * - * <p>This parser fails silently and returns {@code null} if the content of the input stream - * does not follow the schema or has unsupported values. + * <p>This parser fails with an exception if the content of the input stream does not follow the + * schema or has unsupported values. * - * @return the {@link VibrationEffect} if parsed successfully, {@code null} otherwise. - * @throws IOException error reading from given {@link Reader} + * @return the parsed {@link VibrationEffect} + * @throws IOException error reading from given {@link InputStream} or parsing the content. * * @hide */ - @TestApi - @Nullable + @TestApi // Replacing test APIs used in CTS before the flagged system APIs was introduced. + @SystemApi + @FlaggedApi(FLAG_VIBRATION_XML_APIS) + @NonNull + public static VibrationEffect parseVibrationEffect(@NonNull InputStream inputStream) + throws IOException { + return parseVibrationEffect(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + } + + /** + * Parses XML content from given {@link Reader} into a {@link VibrationEffect}. + * + * <p>Same as {@link #parseVibrationEffect(InputStream)}, but with a {@link Reader}. + * + * @hide + */ + @NonNull public static VibrationEffect parseVibrationEffect(@NonNull Reader reader) throws IOException { return parseVibrationEffect(reader, /* flags= */ 0); } /** - * Parses XML content from given input stream into a {@link VibrationEffect}. - * - * <p>This method parses an XML content that contains a single, complete {@link VibrationEffect} - * serialization. As such, the root tag must be a "vibration" tag. + * Parses XML content from given {@link Reader} into a {@link VibrationEffect}. * * <p>Same as {@link #parseVibrationEffect(Reader)}, with extra flags to control the parsing * behavior. * * @hide */ - @Nullable + @NonNull public static VibrationEffect parseVibrationEffect(@NonNull Reader reader, @Flags int flags) throws IOException { - try { - return parseDocumentInternal( - reader, flags, VibrationXmlParser::parseVibrationEffectInternal); - } catch (XmlParserException | XmlPullParserException e) { - Slog.w(TAG, "Error parsing vibration XML", e); - return null; - } + return parseDocumentInternal(reader, flags, + VibrationXmlParser::parseVibrationEffectInternal); } /** - * Parses XML content from given input stream into a {@link ParsedVibration}. + * Parses XML content from given {@link Reader} into a {@link ParsedVibration}. * - * <p>It supports both the "vibration" and "vibration-select" root tags. - * <ul> - * <li>If "vibration" is the root tag, the serialization provided through {@code reader} - * should contain a valid serialization for a single vibration. - * <li>If "vibration-select" is the root tag, the serialization may contain one or more - * valid vibration serializations. - * </ul> - * - * <p>After parsing, it returns a {@link ParsedVibration} that opaquely represents the parsed - * vibration(s), and the caller can get a concrete {@link VibrationEffect} by resolving this - * result to a specific vibrator. - * - * <p>This parser fails silently and returns {@code null} if the content of the input does not - * follow the schema or has unsupported values. - * - * @return a {@link ParsedVibration} - * @throws IOException error reading from given {@link Reader} + * <p>Same as {@link #parse(InputStream)}, but with a {@link Reader}. * * @hide */ - @TestApi - @Nullable + @NonNull public static ParsedVibration parseDocument(@NonNull Reader reader) throws IOException { return parseDocument(reader, /* flags= */ 0); } /** - * Parses XML content from given input stream into a {@link ParsedVibration}. + * Parses XML content from given {@link Reader} into a {@link ParsedVibration}. * * <p>Same as {@link #parseDocument(Reader)}, with extra flags to control the parsing behavior. * * @hide */ - @Nullable + @NonNull public static ParsedVibration parseDocument(@NonNull Reader reader, @Flags int flags) throws IOException { - try { - return parseDocumentInternal(reader, flags, VibrationXmlParser::parseElementInternal); - } catch (XmlParserException | XmlPullParserException e) { - Slog.w(TAG, "Error parsing vibration/vibration-select XML", e); - return null; - } + return parseDocumentInternal(reader, flags, VibrationXmlParser::parseElementInternal); } /** @@ -262,7 +283,7 @@ public final class VibrationXmlParser { * {@link ParsedVibration}. * * <p>Same as {@link #parseDocument(Reader, int)}, but, instead of parsing the full XML content, - * it takes a parser that points to either a <vibration-effect> or a <vibration-select> start + * it takes a parser that points to either a "vibration-effect" or a "vibration-select" start * tag. No other parser position, including start of document, is considered valid. * * <p>This method parses until an end "vibration-effect" or "vibration-select" tag (depending @@ -270,37 +291,22 @@ public final class VibrationXmlParser { * will point to the end tag. * * @throws IOException error parsing from given {@link TypedXmlPullParser}. - * @throws VibrationXmlParserException if the XML tag cannot be parsed into a - * {@link ParsedVibration}. The given {@code parser} might be pointing to a child XML tag - * that caused the parser failure. + * The given {@code parser} might be pointing to a child XML tag that caused the parser + * failure. * * @hide */ @NonNull public static ParsedVibration parseElement(@NonNull TypedXmlPullParser parser, @Flags int flags) - throws IOException, VibrationXmlParserException { + throws IOException { try { return parseElementInternal(parser, flags); } catch (XmlParserException e) { - throw new VibrationXmlParserException("Error parsing vibration-select.", e); - } - } - - /** - * Represents an error while parsing a vibration XML input. - * - * @hide - */ - public static final class VibrationXmlParserException extends Exception { - private VibrationXmlParserException(String message, Throwable cause) { - super(message, cause); - } - - private VibrationXmlParserException(String message) { - super(message); + throw new ParseFailedException(e); } } + @NonNull private static ParsedVibration parseElementInternal( @NonNull TypedXmlPullParser parser, @Flags int flags) throws IOException, XmlParserException { @@ -313,11 +319,12 @@ public final class VibrationXmlParser { case XmlConstants.TAG_VIBRATION_SELECT: return parseVibrationSelectInternal(parser, flags); default: - throw new XmlParserException( - "Unexpected tag name when parsing element: " + tagName); + throw new ParseFailedException( + "Unexpected tag " + tagName + " when parsing a vibration"); } } + @NonNull private static ParsedVibration parseVibrationSelectInternal( @NonNull TypedXmlPullParser parser, @Flags int flags) throws IOException, XmlParserException { @@ -332,7 +339,7 @@ public final class VibrationXmlParser { return new ParsedVibration(effects); } - /** Parses a single XML element for "vibration" tag into a {@link VibrationEffect}. */ + @NonNull private static VibrationEffect parseVibrationEffectInternal( @NonNull TypedXmlPullParser parser, @Flags int flags) throws IOException, XmlParserException { @@ -347,32 +354,60 @@ public final class VibrationXmlParser { * This method parses a whole XML document (provided through a {@link Reader}). The root tag is * parsed as per a provided {@link ElementParser}. */ + @NonNull private static <T> T parseDocumentInternal( @NonNull Reader reader, @Flags int flags, ElementParser<T> parseLogic) - throws IOException, XmlParserException, XmlPullParserException { - TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(reader); + throws IOException { + try { + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(reader); - // Ensure XML starts with a document start tag. - XmlReader.readDocumentStart(parser); + // Ensure XML starts with a document start tag. + XmlReader.readDocumentStart(parser); - // Parse root tag. - T result = parseLogic.parse(parser, flags); + // Parse root tag. + T result = parseLogic.parse(parser, flags); - // Ensure XML ends after root tag is consumed. - XmlReader.readDocumentEndTag(parser); + // Ensure XML ends after root tag is consumed. + XmlReader.readDocumentEndTag(parser); - return result; + return result; + } catch (XmlPullParserException e) { + throw new ParseFailedException("Error initializing XMLPullParser", e); + } catch (XmlParserException e) { + throw new ParseFailedException(e); + } } /** Encapsulate a logic to parse an XML element from an open parser. */ private interface ElementParser<T> { /** Parses a single XML element starting from the current position of the {@code parser}. */ + @NonNull T parse(@NonNull TypedXmlPullParser parser, @Flags int flags) throws IOException, XmlParserException; } + /** + * Represents an error while parsing a vibration XML input. + * + * @hide + */ + @TestApi + public static final class ParseFailedException extends IOException { + private ParseFailedException(String message) { + super(message); + } + + private ParseFailedException(XmlParserException parserException) { + this(parserException.getMessage(), parserException); + } + + private ParseFailedException(String message, Throwable cause) { + super(message, cause); + } + } + private VibrationXmlParser() { } } diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java index 2065d5d0a5a7..a26c6f434e15 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java @@ -18,9 +18,7 @@ package android.os.vibrator.persistence; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.SuppressLint; import android.annotation.TestApi; -import android.os.CombinedVibration; import android.os.VibrationEffect; import android.util.Xml; @@ -37,14 +35,13 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Serializes {@link CombinedVibration} and {@link VibrationEffect} instances to XML. + * Serializes {@link VibrationEffect} instances to XML. * * <p>This uses the same schema expected by the {@link VibrationXmlParser}. * * @hide */ @TestApi -@SuppressLint("UnflaggedApi") // @TestApi without associated feature. public final class VibrationXmlSerializer { /** @@ -80,20 +77,19 @@ public final class VibrationXmlSerializer { "http://xmlpull.org/v1/doc/features.html#indent-output"; /** - * Serializes a {@link VibrationEffect} to XML and writes output to given {@link Writer}. + * Serializes a {@link VibrationEffect} to XML and writes output to given {@link Writer} using + * UTF-8 encoding. * - * <p>This method will only write into the {@link Writer} if the effect can successfully - * be represented by the XML serialization. It will throw an exception otherwise. + * <p>This method will only write to the stream if the effect can successfully be represented by + * the XML serialization. It will throw an exception otherwise. * - * @throws SerializationFailedException serialization of input effect failed, no data was - * written into given {@link Writer}. - * @throws IOException error writing to given {@link Writer}. + * @throws IOException serialization of input effect failed or error writing to output stream. * * @hide */ @TestApi public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer) - throws SerializationFailedException, IOException { + throws IOException { serialize(effect, writer, /* flags= */ 0); } @@ -106,7 +102,7 @@ public final class VibrationXmlSerializer { * @hide */ public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer, - @Flags int flags) throws SerializationFailedException, IOException { + @Flags int flags) throws IOException { // Serialize effect first to fail early. XmlSerializedVibration<VibrationEffect> serializedVibration = toSerializedVibration(effect, flags); @@ -138,17 +134,16 @@ public final class VibrationXmlSerializer { } /** - * Exception thrown when a {@link VibrationEffect} instance serialization fails. + * Exception thrown when a {@link VibrationEffect} serialization fails. * * <p>The serialization can fail if a given vibration cannot be represented using the public - * format, or if it uses hidden APIs that are not supported for serialization (e.g. - * {@link VibrationEffect.WaveformBuilder}). + * format, or if it uses a non-public representation that is not supported for serialization. * * @hide */ @TestApi - public static final class SerializationFailedException extends RuntimeException { - SerializationFailedException(VibrationEffect effect, Throwable cause) { + public static final class SerializationFailedException extends IOException { + private SerializationFailedException(VibrationEffect effect, Throwable cause) { super("Serialization failed for vibration effect " + effect, cause); } } diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 141ffc9a491a..a698b180dbe0 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -374,20 +374,22 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis public @NonNull List<PermissionGroupUsage> getOpUsageDataForAllDevices( boolean includeMicrophoneUsage) { List<PermissionGroupUsage> allUsages = new ArrayList<>(); - List<VirtualDevice> virtualDevices = mVirtualDeviceManager.getVirtualDevices(); - ArraySet<String> persistentDeviceIds = new ArraySet<>(); - for (int num = 0; num < virtualDevices.size(); num++) { - persistentDeviceIds.add(virtualDevices.get(num).getPersistentDeviceId()); - } - persistentDeviceIds.add(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + if (mVirtualDeviceManager != null) { + List<VirtualDevice> virtualDevices = mVirtualDeviceManager.getVirtualDevices(); + ArraySet<String> persistentDeviceIds = new ArraySet<>(); - for (int index = 0; index < persistentDeviceIds.size(); index++) { - allUsages.addAll( - getOpUsageDataByDevice(includeMicrophoneUsage, - persistentDeviceIds.valueAt(index))); - } + for (int num = 0; num < virtualDevices.size(); num++) { + persistentDeviceIds.add(virtualDevices.get(num).getPersistentDeviceId()); + } + persistentDeviceIds.add(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + for (int index = 0; index < persistentDeviceIds.size(); index++) { + allUsages.addAll( + getOpUsageDataByDevice(includeMicrophoneUsage, + persistentDeviceIds.valueAt(index))); + } + } return allUsages; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d91508fe1612..4e8a04f0993f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11046,6 +11046,12 @@ public final class Settings { public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled"; /** + * Whether or not mandatory biometrics is enabled. + * @hide + */ + public static final String MANDATORY_BIOMETRICS = "mandatory_biometrics"; + + /** * Whether or not active unlock triggers on wake. * @hide */ diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig index d6425c397bbb..ed20207cfb0b 100644 --- a/core/java/android/service/chooser/flags.aconfig +++ b/core/java/android/service/chooser/flags.aconfig @@ -32,3 +32,14 @@ flag { description: "Provides additional callbacks with information about user actions in ChooserResult" bug: "263474465" } + +flag { + name: "fix_resolver_memory_leak" + is_exported: true + namespace: "intentresolver" + description: "ResolverActivity memory leak (through the AppPredictor callback) fix" + bug: "346671041" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/service/dreams/DreamOverlayConnectionHandler.java b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java index 85a13c796ee2..bc034008f17c 100644 --- a/core/java/android/service/dreams/DreamOverlayConnectionHandler.java +++ b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java @@ -27,7 +27,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ObservableServiceConnection; -import com.android.internal.util.PersistentServiceConnection; import java.util.ArrayList; import java.util.List; @@ -48,22 +47,20 @@ public final class DreamOverlayConnectionHandler { private static final int MSG_OVERLAY_CLIENT_READY = 3; private final Handler mHandler; - private final PersistentServiceConnection<IDreamOverlay> mConnection; + private final ObservableServiceConnection<IDreamOverlay> mConnection; // Retrieved Client private IDreamOverlayClient mClient; // A list of pending requests to execute on the overlay. private final List<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>(); private final OverlayConnectionCallback mCallback; + private final Runnable mOnDisconnected; DreamOverlayConnectionHandler( Context context, Looper looper, Intent serviceIntent, - int minConnectionDurationMs, - int maxReconnectAttempts, - int baseReconnectDelayMs) { - this(context, looper, serviceIntent, minConnectionDurationMs, maxReconnectAttempts, - baseReconnectDelayMs, new Injector()); + Runnable onDisconnected) { + this(context, looper, serviceIntent, onDisconnected, new Injector()); } @VisibleForTesting @@ -71,20 +68,15 @@ public final class DreamOverlayConnectionHandler { Context context, Looper looper, Intent serviceIntent, - int minConnectionDurationMs, - int maxReconnectAttempts, - int baseReconnectDelayMs, + Runnable onDisconnected, Injector injector) { mCallback = new OverlayConnectionCallback(); mHandler = new Handler(looper, new OverlayHandlerCallback()); + mOnDisconnected = onDisconnected; mConnection = injector.buildConnection( context, mHandler, - serviceIntent, - minConnectionDurationMs, - maxReconnectAttempts, - baseReconnectDelayMs - ); + serviceIntent); } /** @@ -201,10 +193,14 @@ public final class DreamOverlayConnectionHandler { @Override public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection, int reason) { + Log.i(TAG, "Dream overlay disconnected, reason: " + reason); mClient = null; // Cancel any pending messages about the overlay being ready, since it is no // longer ready. mHandler.removeMessages(MSG_OVERLAY_CLIENT_READY); + if (mOnDisconnected != null) { + mOnDisconnected.run(); + } } } @@ -217,25 +213,18 @@ public final class DreamOverlayConnectionHandler { * Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden * in tests with a fake clock. */ - public PersistentServiceConnection<IDreamOverlay> buildConnection( + public ObservableServiceConnection<IDreamOverlay> buildConnection( Context context, Handler handler, - Intent serviceIntent, - int minConnectionDurationMs, - int maxReconnectAttempts, - int baseReconnectDelayMs) { + Intent serviceIntent) { final Executor executor = handler::post; final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; - return new PersistentServiceConnection<>( + return new ObservableServiceConnection<>( context, executor, - handler, IDreamOverlay.Stub::asInterface, serviceIntent, - flags, - minConnectionDurationMs, - maxReconnectAttempts, - baseReconnectDelayMs + flags ); } } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index a769643ce976..8ecb1fb4e9ad 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -182,6 +182,7 @@ public class DreamService extends Service implements Window.Callback { /** * The name of the dream manager service. + * * @hide */ public static final String DREAM_SERVICE = "dreams"; @@ -222,12 +223,14 @@ public class DreamService extends Service implements Window.Callback { /** * Dream category for Low Light Dream + * * @hide */ public static final int DREAM_CATEGORY_LOW_LIGHT = 1 << 0; /** * Dream category for Home Panel Dream + * * @hide */ public static final int DREAM_CATEGORY_HOME_PANEL = 1 << 1; @@ -295,7 +298,8 @@ public class DreamService extends Service implements Window.Callback { void init(Context context); /** Creates and returns the dream overlay connection */ - DreamOverlayConnectionHandler createOverlayConnection(ComponentName overlayComponent); + DreamOverlayConnectionHandler createOverlayConnection(ComponentName overlayComponent, + Runnable onDisconnected); /** Returns the {@link DreamActivity} component */ ComponentName getDreamActivityComponent(); @@ -333,16 +337,15 @@ public class DreamService extends Service implements Window.Callback { @Override public DreamOverlayConnectionHandler createOverlayConnection( - ComponentName overlayComponent) { + ComponentName overlayComponent, + Runnable onDisconnected) { final Resources resources = mContext.getResources(); return new DreamOverlayConnectionHandler( /* context= */ mContext, Looper.getMainLooper(), new Intent().setComponent(overlayComponent), - resources.getInteger(R.integer.config_minDreamOverlayDurationMs), - resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts), - resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs)); + onDisconnected); } @Override @@ -1176,7 +1179,8 @@ public class DreamService extends Service implements Window.Callback { // Connect to the overlay service if present. if (!mWindowless && overlayComponent != null) { - mOverlayConnection = mInjector.createOverlayConnection(overlayComponent); + mOverlayConnection = mInjector.createOverlayConnection(overlayComponent, + this::finish); if (!mOverlayConnection.bind()) { // Binding failed. diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 9c1494660e8b..63410c787540 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -15,6 +15,8 @@ */ package android.service.notification; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; @@ -62,7 +64,8 @@ public final class Adjustment implements Parcelable { KEY_IMPORTANCE_PROPOSAL, KEY_SENSITIVE_CONTENT, KEY_RANKING_SCORE, - KEY_NOT_CONVERSATION + KEY_NOT_CONVERSATION, + KEY_TYPE }) @Retention(RetentionPolicy.SOURCE) public @interface Keys {} @@ -172,6 +175,59 @@ public final class Adjustment implements Parcelable { public static final String KEY_NOT_CONVERSATION = "key_not_conversation"; /** + * Data type: int, the classification type of this notification. The OS may display + * notifications differently depending on the type, and may change the alerting level of the + * notification. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final String KEY_TYPE = "key_type"; + + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_UNKNOWN, + TYPE_OTHER, + TYPE_PROMOTION, + TYPE_SOCIAL_MEDIA, + TYPE_NEWS, + TYPE_CONTENT_RECOMMENDATION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Types {} + + /** + * The type of this notification is unknown. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final int TYPE_UNKNOWN = -1; + /** + * The type of this notification is not one of ones known to the NotificationAssistantService. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final int TYPE_OTHER = 0; + /** + * The type of this notification is a promotion/deal. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final int TYPE_PROMOTION = 1; + /** + * The type of this notification is social media content that isn't a + * {@link Notification.Builder#setShortcutId(String) conversation}. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final int TYPE_SOCIAL_MEDIA = 2; + /** + * The type of this notification is news. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final int TYPE_NEWS = 3; + /** + * The type of this notification is content recommendation, for example new videos or books the + * user may be interested in. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final int TYPE_CONTENT_RECOMMENDATION = 4; + + /** * Create a notification adjustment. * * @param pkg The package of the notification. diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 7a0c016fa0c6..863a99adacca 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -262,15 +262,12 @@ public class ZenModeConfig implements Parcelable { private static final String CONDITION_ATT_SOURCE = "source"; private static final String CONDITION_ATT_FLAGS = "flags"; - private static final String ZEN_POLICY_TAG = "zen_policy"; - private static final String MANUAL_TAG = "manual"; private static final String AUTOMATIC_TAG = "automatic"; private static final String AUTOMATIC_DELETED_TAG = "deleted"; private static final String RULE_ATT_ID = "ruleId"; private static final String RULE_ATT_ENABLED = "enabled"; - private static final String RULE_ATT_SNOOZING = "snoozing"; private static final String RULE_ATT_NAME = "name"; private static final String RULE_ATT_PKG = "pkg"; private static final String RULE_ATT_COMPONENT = "component"; @@ -286,6 +283,7 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_ICON = "rule_icon"; private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc"; private static final String RULE_ATT_DELETION_INSTANT = "deletionInstant"; + private static final String RULE_ATT_DISABLED_ORIGIN = "disabledOrigin"; private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale"; private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = @@ -1170,6 +1168,10 @@ public class ZenModeConfig implements Parcelable { if (deletionInstant != null) { rt.deletionInstant = Instant.ofEpochMilli(deletionInstant); } + if (Flags.modesUi()) { + rt.disabledOrigin = safeInt(parser, RULE_ATT_DISABLED_ORIGIN, + UPDATE_ORIGIN_UNKNOWN); + } } return rt; } @@ -1224,6 +1226,9 @@ public class ZenModeConfig implements Parcelable { out.attributeLong(null, RULE_ATT_DELETION_INSTANT, rule.deletionInstant.toEpochMilli()); } + if (Flags.modesUi()) { + out.attributeInt(null, RULE_ATT_DISABLED_ORIGIN, rule.disabledOrigin); + } } } @@ -2373,7 +2378,7 @@ public class ZenModeConfig implements Parcelable { public int userId = UserHandle.USER_NULL; // USER_NULL = unspecified - use current user public String calName; // CalendarContract.Calendars.DISPLAY_NAME, or null for any - public Long calendarId; // Calendars._ID, or null if restored from < Q calendar + @Nullable public Long calendarId; // Calendars._ID, or null if restored from < Q calendar public int reply; @Override @@ -2405,6 +2410,33 @@ public class ZenModeConfig implements Parcelable { } } + // ==== Built-in system condition: custom manual ==== + + public static final String CUSTOM_MANUAL_PATH = "custom_manual"; + private static final Uri CUSTOM_MANUAL_CONDITION_ID = + new Uri.Builder().scheme(Condition.SCHEME) + .authority(SYSTEM_AUTHORITY) + .appendPath(CUSTOM_MANUAL_PATH) + .build(); + + /** Returns the condition id used for manual (not automatically triggered) custom rules. */ + public static Uri toCustomManualConditionId() { + return CUSTOM_MANUAL_CONDITION_ID; + } + + /** + * Returns whether the supplied {@link Uri} corresponds to the condition id used for manual (not + * automatically triggered) custom rules. + */ + public static boolean isValidCustomManualConditionId(Uri conditionId) { + return CUSTOM_MANUAL_CONDITION_ID.equals(conditionId); + } + + /** Returns the {@link ComponentName} of the custom manual condition provider. */ + public static ComponentName getCustomManualConditionProvider() { + return new ComponentName(SYSTEM_AUTHORITY, "CustomManualConditionProvider"); + } + // ==== End built-in system conditions ==== private static int[] tryParseHourAndMinute(String value) { @@ -2487,6 +2519,8 @@ public class ZenModeConfig implements Parcelable { @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields; @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields; @Nullable public Instant deletionInstant; // Only set on deleted rules. + @FlaggedApi(Flags.FLAG_MODES_UI) + @ConfigChangeOrigin public int disabledOrigin = UPDATE_ORIGIN_UNKNOWN; public ZenRule() { } @@ -2525,6 +2559,9 @@ public class ZenModeConfig implements Parcelable { if (source.readInt() == 1) { deletionInstant = Instant.ofEpochMilli(source.readLong()); } + if (Flags.modesUi()) { + disabledOrigin = source.readInt(); + } } } @@ -2599,6 +2636,9 @@ public class ZenModeConfig implements Parcelable { } else { dest.writeInt(0); } + if (Flags.modesUi()) { + dest.writeInt(disabledOrigin); + } } } @@ -2644,6 +2684,9 @@ public class ZenModeConfig implements Parcelable { if (deletionInstant != null) { sb.append(",deletionInstant=").append(deletionInstant); } + if (Flags.modesUi()) { + sb.append(",disabledOrigin=").append(disabledOrigin); + } } return sb.append(']').toString(); @@ -2697,7 +2740,7 @@ public class ZenModeConfig implements Parcelable { && other.modified == modified; if (Flags.modesApi()) { - return finalEquals + finalEquals = finalEquals && Objects.equals(other.zenDeviceEffects, zenDeviceEffects) && other.allowManualInvocation == allowManualInvocation && Objects.equals(other.iconResName, iconResName) @@ -2708,6 +2751,11 @@ public class ZenModeConfig implements Parcelable { && other.zenDeviceEffectsUserModifiedFields == zenDeviceEffectsUserModifiedFields && Objects.equals(other.deletionInstant, deletionInstant); + + if (Flags.modesUi()) { + finalEquals = finalEquals + && other.disabledOrigin == disabledOrigin; + } } return finalEquals; @@ -2716,11 +2764,21 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { if (Flags.modesApi()) { - return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, - component, configurationActivity, pkg, id, enabler, zenPolicy, - zenDeviceEffects, modified, allowManualInvocation, iconResName, - triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields, - zenDeviceEffectsUserModifiedFields, deletionInstant); + if (Flags.modesUi()) { + return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, + component, configurationActivity, pkg, id, enabler, zenPolicy, + zenDeviceEffects, modified, allowManualInvocation, iconResName, + triggerDescription, type, userModifiedFields, + zenPolicyUserModifiedFields, + zenDeviceEffectsUserModifiedFields, deletionInstant, disabledOrigin); + } else { + return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, + component, configurationActivity, pkg, id, enabler, zenPolicy, + zenDeviceEffects, modified, allowManualInvocation, iconResName, + triggerDescription, type, userModifiedFields, + zenPolicyUserModifiedFields, + zenDeviceEffectsUserModifiedFields, deletionInstant); + } } return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, modified); diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig index c5b4b415edf0..bdef04164b36 100644 --- a/core/java/android/service/notification/flags.aconfig +++ b/core/java/android/service/notification/flags.aconfig @@ -35,4 +35,12 @@ flag { description: "Guards the new CallStyleNotificationEventsCallback" bug: "305095040" is_fixed_read_only: true +} + +flag { + name: "notification_classification" + is_exported: true + namespace: "systemui" + description: "Allows the NAS to classify notifications" + bug: "343988084" }
\ No newline at end of file diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 8271caf57353..3161ff1e4c00 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -31,7 +31,6 @@ import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION; import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents; import static com.android.window.flags.Flags.noVisibilityEventOnDisplayStateChange; import static com.android.window.flags.Flags.offloadColorExtraction; -import static com.android.window.flags.Flags.windowSessionRelayoutInfo; import android.animation.AnimationHandler; import android.animation.Animator; @@ -302,13 +301,10 @@ public abstract class WallpaperService extends Service { final InsetsState mInsetsState = new InsetsState(); final InsetsSourceControl.Array mTempControls = new InsetsSourceControl.Array(); final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); - final Bundle mSyncSeqIdBundle = windowSessionRelayoutInfo() ? null : new Bundle(); SurfaceControl mSurfaceControl = new SurfaceControl(); - WindowRelayoutResult mRelayoutResult = windowSessionRelayoutInfo() - ? new WindowRelayoutResult(mWinFrames, mMergedConfiguration, mSurfaceControl, - mInsetsState, mTempControls) - : null; + WindowRelayoutResult mRelayoutResult = new WindowRelayoutResult( + mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls); private final Point mSurfaceSize = new Point(); private final Point mLastSurfaceSize = new Point(); @@ -1277,15 +1273,8 @@ public abstract class WallpaperService extends Service { } else { mLayout.surfaceInsets.set(0, 0, 0, 0); } - final int relayoutResult; - if (windowSessionRelayoutInfo()) { - relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight, - View.VISIBLE, 0, 0, 0, mRelayoutResult); - } else { - relayoutResult = mSession.relayoutLegacy(mWindow, mLayout, mWidth, mHeight, - View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration, - mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle); - } + final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight, + View.VISIBLE, 0, 0, 0, mRelayoutResult); final Rect outMaxBounds = mMergedConfiguration.getMergedConfiguration() .windowConfiguration.getMaxBounds(); if (!outMaxBounds.equals(maxBounds)) { diff --git a/core/java/android/util/SequenceUtils.java b/core/java/android/util/SequenceUtils.java new file mode 100644 index 000000000000..f833ce314c91 --- /dev/null +++ b/core/java/android/util/SequenceUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; + +/** + * Utilities to manage an info change seq id to ensure the update is in sync between client and + * system server. This should be used for info that can be updated though multiple IPC channel. + * + * To use it: + * 1. The system server should store the current seq as the source of truth, with initializing to + * {@link #getInitSeq}. + * 2. Whenever a newer info needs to be sent to the client side, the system server should first + * update its seq with {@link #getNextSeq}, then send the new info with the new seq to the client. + * 3. On the client side, when receiving a new info, it should only consume it if it is newer than + * the last received info seq by checking {@link #isIncomingSeqNewer}. + * + * @hide + */ +public final class SequenceUtils { + + private SequenceUtils() { + } + + /** + * Returns {@code true} if the incomingSeq is newer than the curSeq. + */ + public static boolean isIncomingSeqNewer(int curSeq, int incomingSeq) { + // Convert to long for comparison. + final long diff = (long) incomingSeq - curSeq; + // If there has been a sufficiently large jump, assume the sequence has wrapped around. + // For example, when the last seq is MAX_VALUE, the incoming seq will be MIN_VALUE + 1. + // diff = MIN_VALUE + 1 - MAX_VALUE. It is smaller than 0, but should be treated as newer. + return diff > 0 || diff < Integer.MIN_VALUE; + } + + /** Returns the initial seq. */ + public static int getInitSeq() { + return Integer.MIN_VALUE; + } + + /** Returns the next seq. */ + public static int getNextSeq(int seq) { + return seq == Integer.MAX_VALUE + // Skip the initial seq, so that when the app process is relaunched, the incoming + // seq from the server is always treated as newer. + ? getInitSeq() + 1 + : ++seq; + } +} diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index e3e4fc04098b..070d33bb9be6 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -49,18 +49,6 @@ import java.util.List; */ interface IWindowSession { - /** - * Bundle key to store the latest sync seq id for the relayout configuration. - * @see #relayout - */ - const String KEY_RELAYOUT_BUNDLE_SEQID = "seqid"; - /** - * Bundle key to store the latest ActivityWindowInfo associated with the relayout configuration. - * Will only be set if the relayout window is an activity window. - * @see #relayout - */ - const String KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO = "activity_window_info"; - int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, int requestedVisibleTypes, out InputChannel outInputChannel, out InsetsState insetsState, @@ -85,16 +73,6 @@ interface IWindowSession { void remove(IBinder clientToken); /** - * @deprecated - */ - int relayoutLegacy(IWindow window, in WindowManager.LayoutParams attrs, - int requestedWidth, int requestedHeight, int viewVisibility, - int flags, int seq, int lastSyncSeqId, out ClientWindowFrames outFrames, - out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl, - out InsetsState insetsState, out InsetsSourceControl.Array activeControls, - out Bundle bundle); - - /** * Change the parameters of a window. You supply the * new parameters, it returns the new frame of the window on screen (the * position should be ignored) and surface of the window. The surface diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 487214c5c33a..2efa647d3169 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -18,6 +18,7 @@ package android.view; import static android.graphics.PointProto.X; import static android.graphics.PointProto.Y; +import static android.util.SequenceUtils.getInitSeq; import static android.view.InsetsSourceControlProto.LEASH; import static android.view.InsetsSourceControlProto.POSITION; import static android.view.InsetsSourceControlProto.TYPE_NUMBER; @@ -266,6 +267,9 @@ public class InsetsSourceControl implements Parcelable { private @Nullable InsetsSourceControl[] mControls; + /** To make sure the info update between client and system server is in order. */ + private int mSeq = getInitSeq(); + public Array() { } @@ -280,9 +284,18 @@ public class InsetsSourceControl implements Parcelable { readFromParcel(in); } + public int getSeq() { + return mSeq; + } + + public void setSeq(int seq) { + mSeq = seq; + } + /** Updates the current Array to the given Array. */ public void setTo(@NonNull Array other, boolean copyControls) { set(other.mControls, copyControls); + mSeq = other.mSeq; } /** Updates the current controls to the given controls. */ @@ -336,11 +349,13 @@ public class InsetsSourceControl implements Parcelable { public void readFromParcel(Parcel in) { mControls = in.createTypedArray(InsetsSourceControl.CREATOR); + mSeq = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { out.writeTypedArray(mControls, flags); + out.writeInt(mSeq); } public static final @NonNull Creator<Array> CREATOR = new Creator<>() { @@ -362,6 +377,7 @@ public class InsetsSourceControl implements Parcelable { return false; } final InsetsSourceControl.Array other = (InsetsSourceControl.Array) o; + // mSeq is for internal bookkeeping only. return Arrays.equals(mControls, other.mControls); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 21eec67bfe10..bbd9acfd4cd7 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -17,6 +17,7 @@ package android.view; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.util.SequenceUtils.getInitSeq; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.InsetsStateProto.DISPLAY_CUTOUT; @@ -95,6 +96,9 @@ public class InsetsState implements Parcelable { /** The display shape */ private DisplayShape mDisplayShape = DisplayShape.NONE; + /** To make sure the info update between client and system server is in order. */ + private int mSeq = getInitSeq(); + public InsetsState() { mSources = new SparseArray<>(); } @@ -586,6 +590,14 @@ public class InsetsState implements Parcelable { } } + public int getSeq() { + return mSeq; + } + + public void setSeq(int seq) { + mSeq = seq; + } + public void set(InsetsState other) { set(other, false /* copySources */); } @@ -597,6 +609,7 @@ public class InsetsState implements Parcelable { mRoundedCornerFrame.set(other.mRoundedCornerFrame); mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); mDisplayShape = other.getDisplayShape(); + mSeq = other.mSeq; mSources.clear(); for (int i = 0, size = other.mSources.size(); i < size; i++) { final InsetsSource otherSource = other.mSources.valueAt(i); @@ -620,6 +633,7 @@ public class InsetsState implements Parcelable { mRoundedCornerFrame.set(other.mRoundedCornerFrame); mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); mDisplayShape = other.getDisplayShape(); + mSeq = other.mSeq; if (types == 0) { return; } @@ -705,6 +719,7 @@ public class InsetsState implements Parcelable { || !mRoundedCornerFrame.equals(state.mRoundedCornerFrame) || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds) || !mDisplayShape.equals(state.mDisplayShape)) { + // mSeq is for internal bookkeeping only. return false; } @@ -778,6 +793,7 @@ public class InsetsState implements Parcelable { mRoundedCornerFrame.writeToParcel(dest, flags); dest.writeTypedObject(mPrivacyIndicatorBounds, flags); dest.writeTypedObject(mDisplayShape, flags); + dest.writeInt(mSeq); final int size = mSources.size(); dest.writeInt(size); for (int i = 0; i < size; i++) { @@ -803,6 +819,7 @@ public class InsetsState implements Parcelable { mRoundedCornerFrame.readFromParcel(in); mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR); mDisplayShape = in.readTypedObject(DisplayShape.CREATOR); + mSeq = in.readInt(); final int size = in.readInt(); final SparseArray<InsetsSource> sources; if (mSources == null) { diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 79a9f2d38ad9..72d2d3b538b3 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -16,15 +16,6 @@ package android.view; -import static android.os.IInputConstants.INPUT_EVENT_FLAG_CANCELED; -import static android.os.IInputConstants.MOTION_EVENT_FLAG_HOVER_EXIT_PENDING; -import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; -import static android.os.IInputConstants.MOTION_EVENT_FLAG_IS_GENERATED_GESTURE; -import static android.os.IInputConstants.MOTION_EVENT_FLAG_NO_FOCUS_CHANGE; -import static android.os.IInputConstants.INPUT_EVENT_FLAG_TAINTED; -import static android.os.IInputConstants.MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS; -import static android.os.IInputConstants.MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; -import static android.os.IInputConstants.MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; import static android.view.Display.DEFAULT_DISPLAY; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -38,6 +29,7 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Matrix; import android.os.Build; +import android.os.MotionEventFlag; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; @@ -476,7 +468,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * to drop the suspect touches or to take additional precautions to confirm the user's * actual intent. */ - public static final int FLAG_WINDOW_IS_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + public static final int FLAG_WINDOW_IS_OBSCURED = MotionEventFlag.WINDOW_IS_OBSCURED; /** * This flag indicates that the window that received this motion event is partly @@ -493,7 +485,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * obstructed in areas other than the touched location. */ public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = - MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; + MotionEventFlag.WINDOW_IS_PARTIALLY_OBSCURED; /** * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that @@ -501,7 +493,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * prevent generating redundant {@link #ACTION_HOVER_ENTER} events. * @hide */ - public static final int FLAG_HOVER_EXIT_PENDING = MOTION_EVENT_FLAG_HOVER_EXIT_PENDING; + public static final int FLAG_HOVER_EXIT_PENDING = MotionEventFlag.HOVER_EXIT_PENDING; /** * This flag indicates that the event has been generated by a gesture generator. It @@ -509,7 +501,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * * @hide */ - public static final int FLAG_IS_GENERATED_GESTURE = MOTION_EVENT_FLAG_IS_GENERATED_GESTURE; + public static final int FLAG_IS_GENERATED_GESTURE = MotionEventFlag.IS_GENERATED_GESTURE; /** * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}. @@ -522,7 +514,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #ACTION_POINTER_UP * @see #ACTION_CANCEL */ - public static final int FLAG_CANCELED = INPUT_EVENT_FLAG_CANCELED; + public static final int FLAG_CANCELED = MotionEventFlag.CANCELED; /** * This flag indicates that the event will not cause a focus change if it is directed to an @@ -531,7 +523,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * window into focus. * @hide */ - public static final int FLAG_NO_FOCUS_CHANGE = MOTION_EVENT_FLAG_NO_FOCUS_CHANGE; + public static final int FLAG_NO_FOCUS_CHANGE = MotionEventFlag.NO_FOCUS_CHANGE; /** * This flag indicates that this event was modified by or generated from an accessibility @@ -539,7 +531,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @hide */ @TestApi - public static final int FLAG_IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; + public static final int FLAG_IS_ACCESSIBILITY_EVENT = MotionEventFlag.IS_ACCESSIBILITY_EVENT; /** * Private flag that indicates when the system has detected that this motion event @@ -550,7 +542,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #isTainted * @see #setTainted */ - public static final int FLAG_TAINTED = INPUT_EVENT_FLAG_TAINTED; + public static final int FLAG_TAINTED = MotionEventFlag.TAINTED; /** * Private flag indicating that this event was synthesized by the system and should be delivered @@ -566,7 +558,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #setTargetAccessibilityFocus(boolean) */ public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = - MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS; + MotionEventFlag.TARGET_ACCESSIBILITY_FOCUS; /** @hide */ @IntDef(flag = true, prefix = { "FLAG_" }, value = { diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 0bdb4ad645f3..f6535243eaac 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -2812,6 +2812,10 @@ public final class SurfaceControl implements Parcelable { private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>(); private final ArrayMap<SurfaceControl, SurfaceControl> mReparentedSurfaces = new ArrayMap<>(); + // Only non-null if the SurfaceControlRegistry is enabled. This list tracks the set of calls + // made through this transaction object, and is dumped (and cleared) when the transaction is + // later applied. + ArrayList<String> mCalls; Runnable mFreeNativeResources; private static final float[] INVALID_COLOR = {-1, -1, -1}; @@ -2837,13 +2841,28 @@ public final class SurfaceControl implements Parcelable { private Transaction(long nativeObject) { mNativeObject = nativeObject; mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject); - if (!SurfaceControlRegistry.sCallStackDebuggingInitialized) { - SurfaceControlRegistry.initializeCallStackDebugging(); - } + setUpForSurfaceControlRegistry(); } private Transaction(Parcel in) { readFromParcel(in); + setUpForSurfaceControlRegistry(); + } + + /** + * Sets up this transaction for the SurfaceControlRegistry. + */ + private void setUpForSurfaceControlRegistry() { + if (!SurfaceControlRegistry.sCallStackDebuggingInitialized) { + SurfaceControlRegistry.initializeCallStackDebugging(); + } + mCalls = SurfaceControlRegistry.sLogAllTxCallsOnApply + ? new ArrayList<>() + : null; + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "ctor", this, null, null); + } } /** @@ -2893,6 +2912,9 @@ public final class SurfaceControl implements Parcelable { if (mNativeObject != 0) { nativeClearTransaction(mNativeObject); } + if (mCalls != null) { + mCalls.clear(); + } } /** @@ -2904,6 +2926,9 @@ public final class SurfaceControl implements Parcelable { mReparentedSurfaces.clear(); mFreeNativeResources.run(); mNativeObject = 0; + if (mCalls != null) { + mCalls.clear(); + } } /** @@ -2921,7 +2946,10 @@ public final class SurfaceControl implements Parcelable { if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( - "apply", this, null, null); + SurfaceControlRegistry.APPLY, this, null, null); + } + if (mCalls != null) { + mCalls.clear(); } } @@ -4421,6 +4449,14 @@ public final class SurfaceControl implements Parcelable { if (this == other) { return this; } + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "merge", this, null, "otherTx=" + other.getId()); + if (mCalls != null) { + mCalls.addAll(other.mCalls); + other.mCalls.clear(); + } + } mResizedSurfaces.putAll(other.mResizedSurfaces); other.mResizedSurfaces.clear(); mReparentedSurfaces.putAll(other.mReparentedSurfaces); @@ -4472,6 +4508,10 @@ public final class SurfaceControl implements Parcelable { Log.w(TAG, "addTransactionCompletedListener was called but flag is disabled"); return this; } + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setFrameTimeline", this, null, "vsyncId=" + vsyncId); + } nativeSetFrameTimelineVsync(mNativeObject, vsyncId); return this; } @@ -4479,6 +4519,11 @@ public final class SurfaceControl implements Parcelable { /** @hide */ @NonNull public Transaction setFrameTimelineVsync(long frameTimelineVsyncId) { + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setFrameTimelineVsync", this, null, "frameTimelineVsyncId=" + + frameTimelineVsyncId); + } nativeSetFrameTimelineVsync(mNativeObject, frameTimelineVsyncId); return this; } diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index aa3654dd77ac..117b200d054e 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -44,6 +44,8 @@ import java.util.WeakHashMap; */ public class SurfaceControlRegistry { private static final String TAG = "SurfaceControlRegistry"; + // Special constant for identifying the Transaction#apply() calls + static final String APPLY = "apply"; /** * An interface for processing the registered SurfaceControls when the threshold is exceeded. @@ -134,6 +136,10 @@ public class SurfaceControlRegistry { // sCallStackDebuggingEnabled is true. Can be combined with the match name. private static String sCallStackDebuggingMatchCall; + // When set, all calls on a SurfaceControl.Transaction will be stored and logged when the + // transaction is applied. + static boolean sLogAllTxCallsOnApply; + // Mapping of the active SurfaceControls to the elapsed time when they were registered @GuardedBy("sLock") private final WeakHashMap<SurfaceControl, Long> mSurfaceControls; @@ -185,6 +191,7 @@ public class SurfaceControlRegistry { public void setCallStackDebuggingParams(String matchName, String matchCall) { sCallStackDebuggingMatchName = matchName.toLowerCase(); sCallStackDebuggingMatchCall = matchCall.toLowerCase(); + sLogAllTxCallsOnApply = sCallStackDebuggingMatchCall.contains("apply"); } /** @@ -294,13 +301,15 @@ public class SurfaceControlRegistry { sCallStackDebuggingMatchName = SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null) .toLowerCase(); + sLogAllTxCallsOnApply = sCallStackDebuggingMatchCall.contains("apply"); // Only enable stack debugging if any of the match filters are set - sCallStackDebuggingEnabled = (!sCallStackDebuggingMatchCall.isEmpty() - || !sCallStackDebuggingMatchName.isEmpty()); + sCallStackDebuggingEnabled = !sCallStackDebuggingMatchCall.isEmpty() + || !sCallStackDebuggingMatchName.isEmpty(); if (sCallStackDebuggingEnabled) { Log.d(TAG, "Enabling transaction call stack debugging:" + " matchCall=" + sCallStackDebuggingMatchCall - + " matchName=" + sCallStackDebuggingMatchName); + + " matchName=" + sCallStackDebuggingMatchName + + " logCallsWithApply=" + sLogAllTxCallsOnApply); } } @@ -319,15 +328,31 @@ public class SurfaceControlRegistry { if (!sCallStackDebuggingEnabled) { return; } - if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) { - return; - } - final String txMsg = tx != null ? "tx=" + tx.getId() + " ": ""; - final String scMsg = sc != null ? " sc=" + sc.getName() + "": ""; + + final String txMsg = tx != null ? "tx=" + tx.getId() + " " : ""; + final String scMsg = sc != null ? " sc=" + sc.getName() + "" : ""; final String msg = details != null ? call + " (" + txMsg + scMsg + ") " + details : call + " (" + txMsg + scMsg + ")"; - Log.e(TAG, msg, new Throwable()); + if (sLogAllTxCallsOnApply && tx != null) { + if (call == APPLY) { + // Log the apply and dump the calls on that transaction + Log.e(TAG, msg, new Throwable()); + for (int i = 0; i < tx.mCalls.size(); i++) { + Log.d(TAG, " " + tx.mCalls.get(i)); + } + } else if (matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) { + // Otherwise log this call to the transaction if it matches the tracked calls + Log.e(TAG, msg, new Throwable()); + tx.mCalls.add(msg); + } + } else { + // Log this call if it matches the tracked calls + if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) { + return; + } + Log.e(TAG, msg, new Throwable()); + } } /** @@ -388,6 +413,7 @@ public class SurfaceControlRegistry { pw.println("sCallStackDebuggingEnabled=" + sCallStackDebuggingEnabled); pw.println("sCallStackDebuggingMatchName=" + sCallStackDebuggingMatchName); pw.println("sCallStackDebuggingMatchCall=" + sCallStackDebuggingMatchCall); + pw.println("sLogAllTxCallsOnApply=" + sLogAllTxCallsOnApply); } } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3df72e8b142a..8c3390cc6462 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -30607,7 +30607,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link #setPointerIcon(PointerIcon)} for mouse devices. Subclasses may override this to * customize the icon for the given pointer. * - * For example, the pointer icon for a stylus pointer can be resolved in the following way: + * For example, to always show the PointerIcon.TYPE_HANDWRITING icon for a stylus pointer, + * the event can be resolved in the following way: * <code><pre> * @Override * public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { @@ -30617,7 +30618,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * && (toolType == MotionEvent.TOOL_TYPE_STYLUS * || toolType == MotionEvent.TOOL_TYPE_ERASER)) { * // Show this pointer icon only if this pointer is a stylus. - * return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_WAIT); + * return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HANDWRITING); * } * // Use the default logic for determining the pointer icon for other non-stylus pointers, * // like for the mouse cursor. @@ -33898,8 +33899,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected int calculateFrameRateCategory() { int category; switch (getViewRootImpl().intermittentUpdateState()) { - case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT -> category = - FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT; + case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT -> { + if (!sToolkitFrameRateBySizeReadOnlyFlagValue) { + category = FRAME_RATE_CATEGORY_NORMAL; + } else { + // The size based frame rate category can only be LOW or NORMAL. If the size + // based frame rate category is LOW, we shouldn't vote for NORMAL for + // intermittent. + category = Math.min( + mSizeBasedFrameRateCategoryAndReason & ~FRAME_RATE_CATEGORY_REASON_MASK, + FRAME_RATE_CATEGORY_NORMAL); + } + category |= FRAME_RATE_CATEGORY_REASON_INTERMITTENT; + } case ViewRootImpl.INTERMITTENT_STATE_NOT_INTERMITTENT -> category = mSizeBasedFrameRateCategoryAndReason; default -> category = mLastFrameRateCategory; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2377b869e804..1494d21e7769 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -25,8 +25,6 @@ import static android.os.Trace.TRACE_TAG_VIEW; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.DragEvent.ACTION_DRAG_LOCATION; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED; -import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT; @@ -90,6 +88,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; @@ -114,6 +113,7 @@ import static android.view.accessibility.Flags.forceInvertColor; import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle; import static android.view.flags.Flags.addSchandleToVriSurface; import static android.view.flags.Flags.sensitiveContentAppProtection; +import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix; import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly; import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly; import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly; @@ -124,11 +124,11 @@ import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodCl import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import static com.android.window.flags.Flags.activityWindowInfoFlag; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; +import static com.android.window.flags.Flags.insetsControlChangedItem; import static com.android.window.flags.Flags.setScPropertiesInClient; -import static com.android.window.flags.Flags.windowSessionRelayoutInfo; -import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -178,7 +178,6 @@ import android.graphics.Region; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; -import android.hardware.SyncFence; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManagerGlobal; @@ -221,7 +220,6 @@ import android.util.proto.ProtoOutputStream; import android.view.InputDevice.InputSourceClass; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl.Transaction; -import android.view.SurfaceControl.TransactionStats; import android.view.View.AttachInfo; import android.view.View.FocusDirection; import android.view.View.MeasureSpec; @@ -297,7 +295,6 @@ import java.util.OptionalInt; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.function.Consumer; import java.util.function.Predicate; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -1137,7 +1134,7 @@ public final class ViewRootImpl implements ViewParent, */ /** - * A temporary object used so relayoutWindow can return the latest SyncSeqId + * Object for relayoutWindow to return the latest window info, including the SyncSeqId * system. The SyncSeqId system was designed to work without synchronous relayout * window, and actually synchronous relayout window presents a problem. We could have * a sequence like this: @@ -1151,14 +1148,8 @@ public final class ViewRootImpl implements ViewParent, * we get rid of synchronous relayout, until then, we use this bundle to channel the * integer back over relayout. */ - private final Bundle mRelayoutBundle = windowSessionRelayoutInfo() - ? null - : new Bundle(); - - private final WindowRelayoutResult mRelayoutResult = windowSessionRelayoutInfo() - ? new WindowRelayoutResult(mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, - mTempInsets, mTempControls) - : null; + private final WindowRelayoutResult mRelayoutResult = new WindowRelayoutResult( + mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls); private static volatile boolean sAnrReported = false; static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback = @@ -1194,13 +1185,6 @@ public final class ViewRootImpl implements ViewParent, private String mFpsTraceName; private String mLargestViewTraceName; - private final boolean mAppStartInfoTimestampsFlagValue; - @GuardedBy("this") - private boolean mAppStartTimestampsSent = false; - private boolean mAppStartTrackingStarted = false; - private long mRenderThreadDrawStartTimeNs = -1; - private long mFirstFramePresentedTimeNs = -1; - private static boolean sToolkitSetFrameRateReadOnlyFlagValue; private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue; private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; @@ -1318,8 +1302,6 @@ public final class ViewRootImpl implements ViewParent, } else { mSensitiveContentProtectionService = null; } - - mAppStartInfoTimestampsFlagValue = android.app.Flags.appStartInfoTimestamps(); } public static void addFirstDrawHandler(Runnable callback) { @@ -2595,12 +2577,6 @@ public final class ViewRootImpl implements ViewParent, notifySurfaceDestroyed(); } destroySurface(); - - // Reset so they can be sent again for warm starts. - mAppStartTimestampsSent = false; - mAppStartTrackingStarted = false; - mRenderThreadDrawStartTimeNs = -1; - mFirstFramePresentedTimeNs = -1; } } } @@ -4399,30 +4375,6 @@ public final class ViewRootImpl implements ViewParent, reportDrawFinished(t, seqId); } }); - - // Only trigger once per {@link ViewRootImpl} instance, so don't add listener if - // {link mTransactionCompletedTimeNs} has already been set. - if (mAppStartInfoTimestampsFlagValue && !mAppStartTrackingStarted) { - mAppStartTrackingStarted = true; - Transaction transaction = new Transaction(); - transaction.addTransactionCompletedListener(mExecutor, - new Consumer<TransactionStats>() { - @Override - public void accept(TransactionStats transactionStats) { - SyncFence presentFence = transactionStats.getPresentFence(); - if (presentFence.awaitForever()) { - if (mFirstFramePresentedTimeNs == -1) { - // Only trigger once per {@link ViewRootImpl} instance. - mFirstFramePresentedTimeNs = presentFence.getSignalTime(); - maybeSendAppStartTimes(); - } - } - presentFence.close(); - } - }); - applyTransactionOnDraw(transaction); - } - if (DEBUG_BLAST) { Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName()); } @@ -4430,45 +4382,6 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroup.add(this, null /* runnable */); } - private void maybeSendAppStartTimes() { - synchronized (this) { - if (mAppStartTimestampsSent) { - // Don't send timestamps more than once. - return; - } - - // If we already have {@link mRenderThreadDrawStartTimeNs} then pass it through, if not - // post to main thread and check if we have it there. - if (mRenderThreadDrawStartTimeNs != -1) { - sendAppStartTimesLocked(); - } else { - mHandler.post(new Runnable() { - @Override - public void run() { - synchronized (ViewRootImpl.this) { - if (mRenderThreadDrawStartTimeNs == -1) { - return; - } - sendAppStartTimesLocked(); - } - } - }); - } - } - } - - @GuardedBy("this") - private void sendAppStartTimesLocked() { - try { - ActivityManager.getService().reportStartInfoViewTimestamps( - mRenderThreadDrawStartTimeNs, mFirstFramePresentedTimeNs); - mAppStartTimestampsSent = true; - } catch (RemoteException e) { - // Ignore, timestamps may be lost. - if (DBG) Log.d(TAG, "Exception attempting to report start timestamps.", e); - } - } - /** * Helper used to notify the service to block projection when a sensitive * view (the view displays sensitive content) is attached to the window. @@ -5655,13 +5568,7 @@ public final class ViewRootImpl implements ViewParent, registerCallbackForPendingTransactions(); } - long timeNs = SystemClock.uptimeNanos(); mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); - - // Only trigger once per {@link ViewRootImpl} instance. - if (mAppStartInfoTimestampsFlagValue && mRenderThreadDrawStartTimeNs == -1) { - mRenderThreadDrawStartTimeNs = timeNs; - } } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface @@ -7511,6 +7418,8 @@ public final class ViewRootImpl implements ViewParent, final KeyEvent event = (KeyEvent)q.mEvent; if (mView.dispatchKeyEventPreIme(event)) { return FINISH_HANDLED; + } else if (q.forPreImeOnly()) { + return FINISH_NOT_HANDLED; } return FORWARD; } @@ -9258,42 +9167,19 @@ public final class ViewRootImpl implements ViewParent, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq, mLastSyncSeqId); } else { - if (windowSessionRelayoutInfo()) { - relayoutResult = mWindowSession.relayout(mWindow, params, - requestedWidth, requestedHeight, viewVisibility, - insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, - mRelayoutSeq, mLastSyncSeqId, mRelayoutResult); - } else { - relayoutResult = mWindowSession.relayoutLegacy(mWindow, params, - requestedWidth, requestedHeight, viewVisibility, - insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, - mRelayoutSeq, mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, - mSurfaceControl, mTempInsets, mTempControls, mRelayoutBundle); - } + relayoutResult = mWindowSession.relayout(mWindow, params, + requestedWidth, requestedHeight, viewVisibility, + insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, + mRelayoutSeq, mLastSyncSeqId, mRelayoutResult); mRelayoutRequested = true; if (activityWindowInfoFlag() && mPendingActivityWindowInfo != null) { - ActivityWindowInfo outInfo = null; - if (windowSessionRelayoutInfo()) { - outInfo = mRelayoutResult != null ? mRelayoutResult.activityWindowInfo : null; - } else { - try { - outInfo = mRelayoutBundle.getParcelable( - IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, - ActivityWindowInfo.class); - mRelayoutBundle.remove( - IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO); - } catch (IllegalStateException e) { - Log.e(TAG, "Failed to get ActivityWindowInfo from relayout Bundle", e); - } - } + final ActivityWindowInfo outInfo = mRelayoutResult.activityWindowInfo; if (outInfo != null) { mPendingActivityWindowInfo.set(outInfo); } } - final int maybeSyncSeqId = windowSessionRelayoutInfo() - ? mRelayoutResult.syncSeqId - : mRelayoutBundle.getInt(IWindowSession.KEY_RELAYOUT_BUNDLE_SEQID); + final int maybeSyncSeqId = mRelayoutResult.syncSeqId; if (maybeSyncSeqId > 0) { mSyncSeqId = maybeSyncSeqId; } @@ -10007,6 +9893,7 @@ public final class ViewRootImpl implements ViewParent, public static final int FLAG_RESYNTHESIZED = 1 << 4; public static final int FLAG_UNHANDLED = 1 << 5; public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6; + public static final int FLAG_PRE_IME_ONLY = 1 << 7; public QueuedInputEvent mNext; @@ -10014,6 +9901,13 @@ public final class ViewRootImpl implements ViewParent, public InputEventReceiver mReceiver; public int mFlags; + public boolean forPreImeOnly() { + if ((mFlags & FLAG_PRE_IME_ONLY) != 0) { + return true; + } + return false; + } + public boolean shouldSkipIme() { if ((mFlags & FLAG_DELIVER_POST_IME) != 0) { return true; @@ -10040,6 +9934,7 @@ public final class ViewRootImpl implements ViewParent, hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb); hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb); hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb); + hasPrevious = flagToString("FLAG_PRE_IME_ONLY", FLAG_PRE_IME_ONLY, hasPrevious, sb); if (!hasPrevious) { sb.append("0"); } @@ -10096,7 +9991,7 @@ public final class ViewRootImpl implements ViewParent, } @UnsupportedAppUsage - void enqueueInputEvent(InputEvent event, + QueuedInputEvent enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); @@ -10135,6 +10030,7 @@ public final class ViewRootImpl implements ViewParent, } else { scheduleProcessInputEvents(); } + return q; } private void scheduleProcessInputEvents() { @@ -11417,8 +11313,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl.Array activeControls) { - final boolean isFromInsetsControlChangeItem = mIsFromTransactionItem; - mIsFromTransactionItem = false; + final boolean isFromInsetsControlChangeItem; + if (insetsControlChangedItem()) { + isFromInsetsControlChangeItem = mIsFromTransactionItem; + mIsFromTransactionItem = false; + } else { + isFromInsetsControlChangeItem = false; + } final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor == null) { if (isFromInsetsControlChangeItem) { @@ -12456,29 +12357,45 @@ public final class ViewRootImpl implements ViewParent, + "IWindow:%s Session:%s", mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession)); } - mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow, + mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow, this, mImeBackAnimationController); } - private void sendBackKeyEvent(int action) { + /** + * Sends {@link KeyEvent#ACTION_DOWN ACTION_DOWN} and {@link KeyEvent#ACTION_UP ACTION_UP} + * back key events + * + * @param preImeOnly whether the back events should be sent to the pre-ime stage only + * @return whether the event was handled (i.e. onKeyPreIme consumed it if preImeOnly=true) + */ + public boolean injectBackKeyEvents(boolean preImeOnly) { + boolean consumed; + try { + processingBackKey(true); + sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly); + consumed = sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly); + } finally { + processingBackKey(false); + } + return consumed; + } + + private boolean sendBackKeyEvent(int action, boolean preImeOnly) { long when = SystemClock.uptimeMillis(); final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); - enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */); + int flags = preImeOnly ? QueuedInputEvent.FLAG_PRE_IME_ONLY : 0; + QueuedInputEvent q = enqueueInputEvent(ev, null /* receiver */, flags, + true /* processImmediately */); + return (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0; } private void registerCompatOnBackInvokedCallback() { mCompatOnBackInvokedCallback = () -> { - try { - processingBackKey(true); - sendBackKeyEvent(KeyEvent.ACTION_DOWN); - sendBackKeyEvent(KeyEvent.ACTION_UP); - } finally { - processingBackKey(false); - } + injectBackKeyEvents(/* preImeOnly */ false); }; if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) { Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher"); diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index d7d764b6ce75..55f22a631c3b 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -23,7 +23,6 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; -import android.os.Bundle; import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; @@ -349,18 +348,6 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public int relayoutLegacy(IWindow window, WindowManager.LayoutParams inAttrs, - int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq, - int lastSyncSeqId, ClientWindowFrames outFrames, - MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl, - InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, - Bundle outSyncSeqIdBundle) { - return relayoutInner(window, inAttrs, requestedWidth, requestedHeight, viewFlags, flags, - seq, lastSyncSeqId, outFrames, outMergedConfiguration, outSurfaceControl, - outInsetsState, outActiveControls); - } - - @Override public int relayout(IWindow window, WindowManager.LayoutParams inAttrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq, int lastSyncSeqId, WindowRelayoutResult outRelayoutResult) { diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 749f977f5e50..c92593f81558 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -97,7 +97,7 @@ public final class AccessibilityWindowInfo implements Parcelable { /** * Window type: A system window that has the function to control an associated window. */ - @FlaggedApi(Flags.FLAG_ADD_TYPE_WINDOW_CONTROL) + @FlaggedApi(Flags.FLAG_ENABLE_TYPE_WINDOW_CONTROL) public static final int TYPE_WINDOW_CONTROL = 7; /* Special values for window IDs */ @@ -880,7 +880,7 @@ public final class AccessibilityWindowInfo implements Parcelable { * @hide */ public static String typeToString(int type) { - if (Flags.addTypeWindowControl() && type == TYPE_WINDOW_CONTROL) { + if (Flags.enableTypeWindowControl() && type == TYPE_WINDOW_CONTROL) { return "TYPE_WINDOW_CONTROL"; } diff --git a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java b/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java new file mode 100644 index 000000000000..2996ddef6f64 --- /dev/null +++ b/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java @@ -0,0 +1,112 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.accessibility.a11ychecker; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.view.accessibility.AccessibilityNodeInfo; + +/** + * Utility class to create developer-friendly {@link AccessibilityNodeInfo} path Strings for use + * in reporting AccessibilityCheck results. + * + * @hide + */ +public final class AccessibilityNodePathBuilder { + + /** + * Returns the path of the node within its accessibility hierarchy starting from the root node + * down to the given node itself, and prefixed by the package name. This path is not guaranteed + * to be unique. This can return null in case the node's hierarchy changes while scanning. + * + * <p>Each element in the path is represented by its View ID resource name, when available, or + * the + * simple class name if not. The path also includes the index of each child node relative to + * its + * parent. See {@link AccessibilityNodeInfo#getViewIdResourceName()}. + * + * <p>For example, + * "com.example.app:RootElementClassName/parent_resource_name[1]/TargetElementClassName[3]" + * indicates the element has type {@code TargetElementClassName}, and is the third child of an + * element with the resource name {@code parent_resource_name}, which is the first child of an + * element of type {@code RootElementClassName}. + * + * <p>This format is consistent with elements paths in Pre-Launch Reports and the Accessibility + * Scanner, starting from the window's root node instead of the first resource name. + * TODO (b/344607035): link to ClusteringUtils when AATF is merged in main. + */ + public static @Nullable String createNodePath(@NonNull AccessibilityNodeInfo nodeInfo) { + StringBuilder resourceIdBuilder = getNodePathBuilder(nodeInfo); + return resourceIdBuilder == null ? null : String.valueOf(nodeInfo.getPackageName()) + ':' + + resourceIdBuilder; + } + + private static @Nullable StringBuilder getNodePathBuilder(AccessibilityNodeInfo nodeInfo) { + AccessibilityNodeInfo parent = nodeInfo.getParent(); + if (parent == null) { + return new StringBuilder(getShortUiElementName(nodeInfo)); + } + StringBuilder parentNodePath = getNodePathBuilder(parent); + if (parentNodePath == null) { + return null; + } + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + if (!nodeInfo.equals(parent.getChild(i))) { + continue; + } + CharSequence uiElementName = getShortUiElementName(nodeInfo); + if (uiElementName != null) { + parentNodePath.append('/').append(uiElementName).append('[').append(i + 1).append( + ']'); + } else { + parentNodePath.append(":nth-child(").append(i + 1).append(')'); + } + return parentNodePath; + } + return null; + } + + //Returns the part of the element's View ID resource name after the qualifier + // "package_name:id/" or the last '/', when available. Otherwise, returns the element's + // simple class name. + private static CharSequence getShortUiElementName(AccessibilityNodeInfo nodeInfo) { + String viewIdResourceName = nodeInfo.getViewIdResourceName(); + if (viewIdResourceName != null) { + String idQualifier = ":id/"; + int idQualifierStartIndex = viewIdResourceName.indexOf(idQualifier); + int unqualifiedNameStartIndex = idQualifierStartIndex == -1 ? 0 + : (idQualifierStartIndex + idQualifier.length()); + return viewIdResourceName.substring(unqualifiedNameStartIndex); + } + return getSimpleClassName(nodeInfo); + } + + private static CharSequence getSimpleClassName(AccessibilityNodeInfo nodeInfo) { + CharSequence name = nodeInfo.getClassName(); + for (int i = name.length() - 1; i > 0; i--) { + char ithChar = name.charAt(i); + if (ithChar == '.' || ithChar == '$') { + return name.subSequence(i + 1, name.length()); + } + } + return name; + } + + private AccessibilityNodePathBuilder() { + } +} diff --git a/core/java/android/view/accessibility/a11ychecker/Android.bp b/core/java/android/view/accessibility/a11ychecker/Android.bp new file mode 100644 index 000000000000..e5a577c16421 --- /dev/null +++ b/core/java/android/view/accessibility/a11ychecker/Android.bp @@ -0,0 +1,7 @@ +java_library_static { + name: "A11yChecker", + srcs: [ + "*.java", + ], + visibility: ["//visibility:public"], +} diff --git a/core/java/android/view/accessibility/a11ychecker/OWNERS b/core/java/android/view/accessibility/a11ychecker/OWNERS new file mode 100644 index 000000000000..d1e7986cc209 --- /dev/null +++ b/core/java/android/view/accessibility/a11ychecker/OWNERS @@ -0,0 +1,4 @@ +# Android Accessibility Framework owners +include /services/accessibility/OWNERS + +yaraabdullatif@google.com diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 64cf40b81a8a..d0bc57b9afbe 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -136,9 +136,8 @@ flag { flag { namespace: "accessibility" - name: "add_type_window_control" + name: "enable_type_window_control" is_exported: true - is_fixed_read_only: true description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations" bug: "320445550" } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index d74867c31bb6..724e8fa830af 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -18,7 +18,6 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.toSet; -import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -602,26 +601,16 @@ public final class ContentCaptureManager { public ContentCaptureSession getMainContentCaptureSession() { synchronized (mLock) { if (mMainSession == null) { - mMainSession = prepareMainSession(); - if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); - } - return mMainSession; - } - } - - @NonNull - @GuardedBy("mLock") - private ContentCaptureSession prepareMainSession() { - if (runOnBackgroundThreadEnabled()) { - return new MainContentCaptureSessionV2( + mMainSession = new MainContentCaptureSession( mContext, this, prepareUiHandler(), prepareContentCaptureHandler(), mService - ); - } else { - return new MainContentCaptureSession(mContext, this, prepareUiHandler(), mService); + ); + if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); + } + return mMainSession; } } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index a90c94eac967..eb827dd5258d 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -69,16 +69,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -// TODO(b/309411951): Replace V2 as the only main session once the experiment is done. /** * Main session associated with a context. * - * <p>This session is created when the activity starts and finished when it stops; clients can use - * it to create children activities. - * * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @@ -107,7 +104,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private final ContentCaptureManager mManager; @NonNull - private final Handler mHandler; + private final Handler mUiHandler; + + @NonNull + private final Handler mContentCaptureHandler; /** * Interface to the system_server binder object - it's only used to start the session (and @@ -142,6 +142,18 @@ public final class MainContentCaptureSession extends ContentCaptureSession { public ComponentName mComponentName; /** + * Thread-safe queue of events held to be processed as a batch. + * + * Because it is not guaranteed that the events will be enqueued from a single thread, the + * implementation must be thread-safe to prevent unexpected behaviour. + * + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @NonNull + public final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue; + + /** * List of events held to be sent to the {@link ContentCaptureService} as a batch. * * @hide @@ -200,14 +212,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { binder = resultData.getBinder(EXTRA_BINDER); if (binder == null) { Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); - mainSession.mHandler.post(() -> mainSession.resetSession( + mainSession.runOnContentCaptureThread(() -> mainSession.resetSession( STATE_DISABLED | STATE_INTERNAL_ERROR)); return; } } else { binder = null; } - mainSession.mHandler.post(() -> + mainSession.runOnContentCaptureThread(() -> mainSession.onSessionStarted(resultCode, binder)); } } @@ -217,17 +229,21 @@ public final class MainContentCaptureSession extends ContentCaptureSession { public MainContentCaptureSession( @NonNull ContentCaptureManager.StrippedContext context, @NonNull ContentCaptureManager manager, - @NonNull Handler handler, + @NonNull Handler uiHandler, + @NonNull Handler contentCaptureHandler, @NonNull IContentCaptureManager systemServerInterface) { mContext = context; mManager = manager; - mHandler = handler; + mUiHandler = uiHandler; + mContentCaptureHandler = contentCaptureHandler; mSystemServerInterface = systemServerInterface; final int logHistorySize = mManager.mOptions.logHistorySize; mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null; mSessionStateReceiver = new SessionStateReceiver(this); + + mEventProcessQueue = new ConcurrentLinkedQueue<>(); } @Override @@ -248,7 +264,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @Override void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { - checkOnUiThread(); + runOnContentCaptureThread( + () -> startImpl(token, shareableActivityToken, component, flags)); + } + + private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, + @NonNull ComponentName component, int flags) { + checkOnContentCaptureThread(); if (!isContentCaptureEnabled()) return; if (sVerbose) { @@ -282,17 +304,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); } } - @Override void onDestroy() { - mHandler.removeMessages(MSG_FLUSH); - mHandler.post(() -> { + clearAndRunOnContentCaptureThread(() -> { try { flush(FLUSH_REASON_SESSION_FINISHED); } finally { destroySession(); } - }); + }, MSG_FLUSH); } /** @@ -305,7 +325,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void onSessionStarted(int resultCode, @Nullable IBinder binder) { - checkOnUiThread(); + checkOnContentCaptureThread(); if (binder != null) { mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); mDirectServiceVulture = () -> { @@ -324,7 +344,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mContentProtectionEventProcessor = new ContentProtectionEventProcessor( mManager.getContentProtectionEventBuffer(), - mHandler, + mContentCaptureHandler, mSystemServerInterface, mComponentName.getPackageName(), mManager.mOptions.contentProtectionOptions); @@ -354,7 +374,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { - checkOnUiThread(); + checkOnContentCaptureThread(); final int eventType = event.getType(); if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED @@ -398,14 +418,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { - checkOnUiThread(); + checkOnContentCaptureThread(); if (mContentProtectionEventProcessor != null) { mContentProtectionEventProcessor.processEvent(event); } } private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { - checkOnUiThread(); + checkOnContentCaptureThread(); final int eventType = event.getType(); final int maxBufferSize = mManager.mOptions.maxBufferSize; if (mEvents == null) { @@ -540,12 +560,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private boolean hasStarted() { - checkOnUiThread(); + checkOnContentCaptureThread(); return mState != UNKNOWN_STATE; } private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { - checkOnUiThread(); + checkOnContentCaptureThread(); if (sVerbose) { Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) + ", checkExisting=" + checkExisting); @@ -562,9 +582,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession { + "when disabled. events=" + (mEvents == null ? null : mEvents.size())); return; } - if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) { + if (checkExisting && mContentCaptureHandler.hasMessages(MSG_FLUSH)) { // "Renew" the flush message by removing the previous one - mHandler.removeMessages(MSG_FLUSH); + mContentCaptureHandler.removeMessages(MSG_FLUSH); } final int flushFrequencyMs; @@ -586,12 +606,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { + flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush)); } // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage() - mHandler.postDelayed(() -> + mContentCaptureHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); } private void flushIfNeeded(@FlushReason int reason) { - checkOnUiThread(); + checkOnContentCaptureThread(); if (mEvents == null || mEvents.isEmpty()) { if (sVerbose) Log.v(TAG, "Nothing to flush"); return; @@ -603,7 +623,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override public void flush(@FlushReason int reason) { - checkOnUiThread(); + runOnContentCaptureThread(() -> flushImpl(reason)); + } + + private void flushImpl(@FlushReason int reason) { + checkOnContentCaptureThread(); if (mEvents == null || mEvents.size() == 0) { if (sVerbose) { Log.v(TAG, "Don't flush for empty event buffer."); @@ -626,7 +650,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, " + "client not ready: " + mEvents); } - if (!mHandler.hasMessages(MSG_FLUSH)) { + if (!mContentCaptureHandler.hasMessages(MSG_FLUSH)) { scheduleFlush(reason, /* checkExisting= */ false); } return; @@ -652,7 +676,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mFlushHistory.log(logRecord); } try { - mHandler.removeMessages(MSG_FLUSH); + mContentCaptureHandler.removeMessages(MSG_FLUSH); final ParceledListSlice<ContentCaptureEvent> events = clearEvents(); mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions); @@ -672,7 +696,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { */ @NonNull private ParceledListSlice<ContentCaptureEvent> clearEvents() { - checkOnUiThread(); + checkOnContentCaptureThread(); // NOTE: we must save a reference to the current mEvents and then set it to to null, // otherwise clearing it would clear it in the receiving side if the service is also local. if (mEvents == null) { @@ -687,7 +711,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void destroySession() { - checkOnUiThread(); + checkOnContentCaptureThread(); if (sDebug) { Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " @@ -707,6 +731,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } mDirectServiceInterface = null; mContentProtectionEventProcessor = null; + mEventProcessQueue.clear(); } // TODO(b/122454205): once we support multiple sessions, we might need to move some of these @@ -714,7 +739,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void resetSession(int newState) { - checkOnUiThread(); + checkOnContentCaptureThread(); if (sVerbose) { Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " + getStateAsString(mState) + " to " + getStateAsString(newState)); @@ -735,21 +760,21 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } mDirectServiceInterface = null; mContentProtectionEventProcessor = null; - mHandler.removeMessages(MSG_FLUSH); + mContentCaptureHandler.removeMessages(MSG_FLUSH); } @Override void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) .setViewNode(node.mNode); - mHandler.post(() -> sendEvent(event)); + enqueueEvent(event); } @Override void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) { final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED) .setAutofillId(id); - mHandler.post(() -> sendEvent(event)); + enqueueEvent(event); } @Override @@ -780,7 +805,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { .setAutofillId(id).setText(eventText) .setComposingIndex(composingStart, composingEnd) .setSelectionIndex(startIndex, endIndex); - mHandler.post(() -> sendEvent(event)); + enqueueEvent(event); } @Override @@ -788,7 +813,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) .setInsets(viewInsets); - mHandler.post(() -> sendEvent(event)); + enqueueEvent(event); } @Override @@ -798,19 +823,19 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final boolean forceFlush = disableFlush ? !started : FORCE_FLUSH; final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type); - mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); + enqueueEvent(event, forceFlush); } @Override public void internalNotifySessionResumed() { final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_RESUMED); - mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); + enqueueEvent(event, FORCE_FLUSH); } @Override public void internalNotifySessionPaused() { final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_PAUSED); - mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); + enqueueEvent(event, FORCE_FLUSH); } @Override @@ -818,12 +843,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession { return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled(); } - @Override + // Called by ContentCaptureManager.isContentCaptureEnabled boolean isDisabled() { return mDisabled.get(); } - @Override + /** + * Sets the disabled state of content capture. + * + * @return whether disabled state was changed. + */ boolean setDisabled(boolean disabled) { return mDisabled.compareAndSet(!disabled, disabled); } @@ -835,7 +864,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) .setParentSessionId(parentSessionId) .setClientContext(clientContext); - mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); + enqueueEvent(event, FORCE_FLUSH); } @Override @@ -843,14 +872,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final ContentCaptureEvent event = new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) .setParentSessionId(parentSessionId); - mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); + enqueueEvent(event, FORCE_FLUSH); } @Override void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) .setClientContext(context); - mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); + enqueueEvent(event, FORCE_FLUSH); } @Override @@ -858,18 +887,97 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) .setBounds(bounds); - mHandler.post(() -> sendEvent(event)); + enqueueEvent(event); + } + + private List<ContentCaptureEvent> clearBufferEvents() { + final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>(); + ContentCaptureEvent event; + while ((event = mEventProcessQueue.poll()) != null) { + bufferEvents.add(event); + } + return bufferEvents; + } + + private void enqueueEvent(@NonNull final ContentCaptureEvent event) { + enqueueEvent(event, /* forceFlush */ false); + } + + /** + * Enqueue the event into {@code mEventProcessBuffer} if it is not an urgent request. Otherwise, + * clear the buffer events then starting sending out current event. + */ + private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) { + if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) { + // The buffer events are cleared in the same thread first to prevent new events + // being added during the time of context switch. This would disrupt the sequence + // of events. + final List<ContentCaptureEvent> batchEvents = clearBufferEvents(); + runOnContentCaptureThread(() -> { + for (int i = 0; i < batchEvents.size(); i++) { + sendEvent(batchEvents.get(i)); + } + sendEvent(event, /* forceFlush= */ true); + }); + } else { + mEventProcessQueue.offer(event); + } } @Override public void notifyContentCaptureEvents( @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { - notifyContentCaptureEventsImpl(contentCaptureEvents); + runOnUiThread(() -> { + prepareViewStructures(contentCaptureEvents); + runOnContentCaptureThread(() -> + notifyContentCaptureEventsImpl(contentCaptureEvents)); + }); + } + + /** + * Traverse events and pre-process {@link View} events to {@link ViewStructureSession} events. + * If a {@link View} event is invalid, an empty {@link ViewStructureSession} will still be + * provided. + */ + private void prepareViewStructures( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { + for (int i = 0; i < contentCaptureEvents.size(); i++) { + int sessionId = contentCaptureEvents.keyAt(i); + ArrayList<Object> events = contentCaptureEvents.valueAt(i); + for_each_event: for (int j = 0; j < events.size(); j++) { + Object event = events.get(j); + if (event instanceof View) { + View view = (View) event; + ContentCaptureSession session = view.getContentCaptureSession(); + ViewStructureSession structureSession = new ViewStructureSession(); + + // Replace the View event with ViewStructureSession no matter the data is + // available or not. This is to ensure the sequence of the events are still + // the same. Calls to notifyViewAppeared will check the availability later. + events.set(j, structureSession); + if (session == null) { + Log.w(TAG, "no content capture session on view: " + view); + continue for_each_event; + } + int actualId = session.getId(); + if (actualId != sessionId) { + Log.w(TAG, "content capture session mismatch for view (" + view + + "): was " + sessionId + " before, it's " + actualId + " now"); + continue for_each_event; + } + ViewStructure structure = session.newViewStructure(view); + view.onProvideContentCaptureStructure(structure, /* flags= */ 0); + + structureSession.setSession(session); + structureSession.setStructure(structure); + } + } + } } private void notifyContentCaptureEventsImpl( @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { - checkOnUiThread(); + checkOnContentCaptureThread(); try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); @@ -882,22 +990,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Object event = events.get(j); if (event instanceof AutofillId) { internalNotifyViewDisappeared(sessionId, (AutofillId) event); - } else if (event instanceof View) { - View view = (View) event; - ContentCaptureSession session = view.getContentCaptureSession(); - if (session == null) { - Log.w(TAG, "no content capture session on view: " + view); - continue for_each_event; - } - int actualId = session.getId(); - if (actualId != sessionId) { - Log.w(TAG, "content capture session mismatch for view (" + view - + "): was " + sessionId + " before, it's " + actualId + " now"); - continue for_each_event; - } - ViewStructure structure = session.newViewStructure(view); - view.onProvideContentCaptureStructure(structure, /* flags= */ 0); - session.notifyViewAppeared(structure); + } else if (event instanceof ViewStructureSession viewStructureSession) { + viewStructureSession.notifyViewAppeared(); } else if (event instanceof Insets) { internalNotifyViewInsetsChanged(sessionId, (Insets) event); } else { @@ -1015,9 +1109,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * Therefore, accessing internal properties in {@link MainContentCaptureSession} should * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> */ - private void checkOnUiThread() { - final boolean onUiThread = mHandler.getLooper().isCurrentThread(); - if (!onUiThread) { + private void checkOnContentCaptureThread() { + final boolean onContentCaptureThread = mContentCaptureHandler.getLooper().isCurrentThread(); + if (!onContentCaptureThread) { mWrongThreadCount.incrementAndGet(); Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread()); } @@ -1028,4 +1122,63 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Counter.logIncrement( CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID, mWrongThreadCount.getAndSet(0)); } + + /** + * Ensures that {@code r} will be running on the assigned thread. + * + * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable. + * </p> + */ + private void runOnContentCaptureThread(@NonNull Runnable r) { + if (!mContentCaptureHandler.getLooper().isCurrentThread()) { + mContentCaptureHandler.post(r); + } else { + r.run(); + } + } + + private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { + if (!mContentCaptureHandler.getLooper().isCurrentThread()) { + mContentCaptureHandler.removeMessages(what); + mContentCaptureHandler.post(r); + } else { + r.run(); + } + } + + private void runOnUiThread(@NonNull Runnable r) { + if (mUiHandler.getLooper().isCurrentThread()) { + r.run(); + } else { + mUiHandler.post(r); + } + } + + /** + * Holds {@link ContentCaptureSession} and related {@link ViewStructure} for processing. + */ + private static final class ViewStructureSession { + @Nullable private ContentCaptureSession mSession; + @Nullable private ViewStructure mStructure; + + ViewStructureSession() {} + + void setSession(@Nullable ContentCaptureSession session) { + this.mSession = session; + } + + void setStructure(@Nullable ViewStructure struct) { + this.mStructure = struct; + } + + /** + * Calls {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)} if the session and + * the view structure are available. + */ + void notifyViewAppeared() { + if (mSession != null && mStructure != null) { + mSession.notifyViewAppeared(mStructure); + } + } + } } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java deleted file mode 100644 index fbb66d1485dd..000000000000 --- a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java +++ /dev/null @@ -1,1187 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.contentcapture; - -import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_INSETS_CHANGED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_WINDOW_BOUNDS_CHANGED; -import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; -import static android.view.contentcapture.ContentCaptureHelper.sDebug; -import static android.view.contentcapture.ContentCaptureHelper.sVerbose; -import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.pm.ParceledListSlice; -import android.graphics.Insets; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.IBinder.DeathRecipient; -import android.os.RemoteException; -import android.os.Trace; -import android.service.contentcapture.ContentCaptureService; -import android.text.Selection; -import android.text.Spannable; -import android.text.TextUtils; -import android.util.LocalLog; -import android.util.Log; -import android.util.SparseArray; -import android.util.TimeUtils; -import android.view.View; -import android.view.ViewStructure; -import android.view.autofill.AutofillId; -import android.view.contentcapture.ViewNode.ViewStructureImpl; -import android.view.contentprotection.ContentProtectionEventProcessor; -import android.view.inputmethod.BaseInputConnection; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.IResultReceiver; -import com.android.modules.expresslog.Counter; - -import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Main session associated with a context. - * - * <p>This is forked from {@link MainContentCaptureSession} to hold the logic of running operations - * in the background thread.</p> - * - * @hide - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class MainContentCaptureSessionV2 extends ContentCaptureSession { - - private static final String TAG = MainContentCaptureSession.class.getSimpleName(); - - private static final String CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID = - "content_capture.value_content_capture_wrong_thread_count"; - - // For readability purposes... - private static final boolean FORCE_FLUSH = true; - - /** - * Handler message used to flush the buffer. - */ - private static final int MSG_FLUSH = 1; - - @NonNull - private final AtomicBoolean mDisabled = new AtomicBoolean(false); - - @NonNull - private final ContentCaptureManager.StrippedContext mContext; - - @NonNull - private final ContentCaptureManager mManager; - - @NonNull - private final Handler mUiHandler; - - @NonNull - private final Handler mContentCaptureHandler; - - /** - * Interface to the system_server binder object - it's only used to start the session (and - * notify when the session is finished). - */ - @NonNull - private final IContentCaptureManager mSystemServerInterface; - - /** - * Direct interface to the service binder object - it's used to send the events, including the - * last ones (when the session is finished) - * - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @Nullable - public IContentCaptureDirectManager mDirectServiceInterface; - - @Nullable - private DeathRecipient mDirectServiceVulture; - - private int mState = UNKNOWN_STATE; - - @Nullable - private IBinder mApplicationToken; - @Nullable - private IBinder mShareableActivityToken; - - /** @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @Nullable - public ComponentName mComponentName; - - /** - * Thread-safe queue of events held to be processed as a batch. - * - * Because it is not guaranteed that the events will be enqueued from a single thread, the - * implementation must be thread-safe to prevent unexpected behaviour. - * - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @NonNull - public final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue; - - /** - * List of events held to be sent to the {@link ContentCaptureService} as a batch. - * - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @Nullable - public ArrayList<ContentCaptureEvent> mEvents; - - // Used just for debugging purposes (on dump) - private long mNextFlush; - - /** - * Whether the next buffer flush is queued by a text changed event. - */ - private boolean mNextFlushForTextChanged = false; - - @Nullable - private final LocalLog mFlushHistory; - - private final AtomicInteger mWrongThreadCount = new AtomicInteger(0); - - /** - * Binder object used to update the session state. - */ - @NonNull - private final SessionStateReceiver mSessionStateReceiver; - - /** @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @Nullable - public ContentProtectionEventProcessor mContentProtectionEventProcessor; - - private static class SessionStateReceiver extends IResultReceiver.Stub { - private final WeakReference<MainContentCaptureSessionV2> mMainSession; - - SessionStateReceiver(MainContentCaptureSessionV2 session) { - mMainSession = new WeakReference<>(session); - } - - @Override - public void send(int resultCode, Bundle resultData) { - final MainContentCaptureSessionV2 mainSession = mMainSession.get(); - if (mainSession == null) { - Log.w(TAG, "received result after mina session released"); - return; - } - final IBinder binder; - if (resultData != null) { - // Change in content capture enabled. - final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE); - if (hasEnabled) { - final boolean disabled = (resultCode == RESULT_CODE_FALSE); - mainSession.mDisabled.set(disabled); - return; - } - binder = resultData.getBinder(EXTRA_BINDER); - if (binder == null) { - Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); - mainSession.runOnContentCaptureThread(() -> mainSession.resetSession( - STATE_DISABLED | STATE_INTERNAL_ERROR)); - return; - } - } else { - binder = null; - } - mainSession.runOnContentCaptureThread(() -> - mainSession.onSessionStarted(resultCode, binder)); - } - } - - /** @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) - public MainContentCaptureSessionV2( - @NonNull ContentCaptureManager.StrippedContext context, - @NonNull ContentCaptureManager manager, - @NonNull Handler uiHandler, - @NonNull Handler contentCaptureHandler, - @NonNull IContentCaptureManager systemServerInterface) { - mContext = context; - mManager = manager; - mUiHandler = uiHandler; - mContentCaptureHandler = contentCaptureHandler; - mSystemServerInterface = systemServerInterface; - - final int logHistorySize = mManager.mOptions.logHistorySize; - mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null; - - mSessionStateReceiver = new SessionStateReceiver(this); - - mEventProcessQueue = new ConcurrentLinkedQueue<>(); - } - - @Override - ContentCaptureSession getMainCaptureSession() { - return this; - } - - @Override - ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) { - final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext); - internalNotifyChildSessionStarted(mId, child.mId, clientContext); - return child; - } - - /** - * Starts this session. - */ - @Override - void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, - @NonNull ComponentName component, int flags) { - runOnContentCaptureThread( - () -> startImpl(token, shareableActivityToken, component, flags)); - } - - private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, - @NonNull ComponentName component, int flags) { - checkOnContentCaptureThread(); - if (!isContentCaptureEnabled()) return; - - if (sVerbose) { - Log.v(TAG, "start(): token=" + token + ", comp=" - + ComponentName.flattenToShortString(component)); - } - - if (hasStarted()) { - // TODO(b/122959591): make sure this is expected (and when), or use Log.w - if (sDebug) { - Log.d(TAG, "ignoring handleStartSession(" + token + "/" - + ComponentName.flattenToShortString(component) + " while on state " - + getStateAsString(mState)); - } - return; - } - mState = STATE_WAITING_FOR_SERVER; - mApplicationToken = token; - mShareableActivityToken = shareableActivityToken; - mComponentName = component; - - if (sVerbose) { - Log.v(TAG, "handleStartSession(): token=" + token + ", act=" - + getDebugState() + ", id=" + mId); - } - - try { - mSystemServerInterface.startSession(mApplicationToken, mShareableActivityToken, - component, mId, flags, mSessionStateReceiver); - } catch (RemoteException e) { - Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); - } - } - @Override - void onDestroy() { - clearAndRunOnContentCaptureThread(() -> { - try { - flush(FLUSH_REASON_SESSION_FINISHED); - } finally { - destroySession(); - } - }, MSG_FLUSH); - } - - /** - * Callback from {@code system_server} after call to {@link - * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}. - * - * @param resultCode session state - * @param binder handle to {@code IContentCaptureDirectManager} - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public void onSessionStarted(int resultCode, @Nullable IBinder binder) { - checkOnContentCaptureThread(); - if (binder != null) { - mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); - mDirectServiceVulture = () -> { - Log.w(TAG, "Keeping session " + mId + " when service died"); - mState = STATE_SERVICE_DIED; - mDisabled.set(true); - }; - try { - binder.linkToDeath(mDirectServiceVulture, 0); - } catch (RemoteException e) { - Log.w(TAG, "Failed to link to death on " + binder + ": " + e); - } - } - - if (isContentProtectionEnabled()) { - mContentProtectionEventProcessor = - new ContentProtectionEventProcessor( - mManager.getContentProtectionEventBuffer(), - mContentCaptureHandler, - mSystemServerInterface, - mComponentName.getPackageName(), - mManager.mOptions.contentProtectionOptions); - } else { - mContentProtectionEventProcessor = null; - } - - if ((resultCode & STATE_DISABLED) != 0) { - resetSession(resultCode); - } else { - mState = resultCode; - mDisabled.set(false); - // Flush any pending data immediately as buffering forced until now. - flushIfNeeded(FLUSH_REASON_SESSION_CONNECTED); - } - if (sVerbose) { - Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode - + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get() - + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size())); - } - } - - /** @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public void sendEvent(@NonNull ContentCaptureEvent event) { - sendEvent(event, /* forceFlush= */ false); - } - - private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { - checkOnContentCaptureThread(); - final int eventType = event.getType(); - if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); - if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED - && eventType != ContentCaptureEvent.TYPE_CONTEXT_UPDATED) { - // TODO(b/120494182): comment when this could happen (dialogs?) - if (sVerbose) { - Log.v(TAG, "handleSendEvent(" + getDebugState() + ", " - + ContentCaptureEvent.getTypeAsString(eventType) - + "): dropping because session not started yet"); - } - return; - } - if (mDisabled.get()) { - // This happens when the event was queued in the handler before the sesison was ready, - // then handleSessionStarted() returned and set it as disabled - we need to drop it, - // otherwise it will keep triggering handleScheduleFlush() - if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled"); - return; - } - - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - if (eventType == TYPE_VIEW_TREE_APPEARING) { - Trace.asyncTraceBegin( - Trace.TRACE_TAG_VIEW, /* methodName= */ "sendEventAsync", /* cookie= */ 0); - } - } - - if (isContentProtectionReceiverEnabled()) { - sendContentProtectionEvent(event); - } - if (isContentCaptureReceiverEnabled()) { - sendContentCaptureEvent(event, forceFlush); - } - - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - if (eventType == TYPE_VIEW_TREE_APPEARED) { - Trace.asyncTraceEnd( - Trace.TRACE_TAG_VIEW, /* methodName= */ "sendEventAsync", /* cookie= */ 0); - } - } - } - - private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { - checkOnContentCaptureThread(); - if (mContentProtectionEventProcessor != null) { - mContentProtectionEventProcessor.processEvent(event); - } - } - - private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { - checkOnContentCaptureThread(); - final int eventType = event.getType(); - final int maxBufferSize = mManager.mOptions.maxBufferSize; - if (mEvents == null) { - if (sVerbose) { - Log.v(TAG, "handleSendEvent(): creating buffer for " + maxBufferSize + " events"); - } - mEvents = new ArrayList<>(maxBufferSize); - } - - // Some type of events can be merged together - boolean addEvent = true; - - if (eventType == TYPE_VIEW_TEXT_CHANGED) { - // We determine whether to add or merge the current event by following criteria: - // 1. Don't have composing span: always add. - // 2. Have composing span: - // 2.1 either last or current text is empty: add. - // 2.2 last event doesn't have composing span: add. - // Otherwise, merge. - final CharSequence text = event.getText(); - final boolean hasComposingSpan = event.hasComposingSpan(); - if (hasComposingSpan) { - ContentCaptureEvent lastEvent = null; - for (int index = mEvents.size() - 1; index >= 0; index--) { - final ContentCaptureEvent tmpEvent = mEvents.get(index); - if (event.getId().equals(tmpEvent.getId())) { - lastEvent = tmpEvent; - break; - } - } - if (lastEvent != null && lastEvent.hasComposingSpan()) { - final CharSequence lastText = lastEvent.getText(); - final boolean bothNonEmpty = !TextUtils.isEmpty(lastText) - && !TextUtils.isEmpty(text); - boolean equalContent = - TextUtils.equals(lastText, text) - && lastEvent.hasSameComposingSpan(event) - && lastEvent.hasSameSelectionSpan(event); - if (equalContent) { - addEvent = false; - } else if (bothNonEmpty) { - lastEvent.mergeEvent(event); - addEvent = false; - } - if (!addEvent && sVerbose) { - Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text=" - + getSanitizedString(text)); - } - } - } - } - - if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) { - final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1); - if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED - && event.getSessionId() == lastEvent.getSessionId()) { - if (sVerbose) { - Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session " - + lastEvent.getSessionId()); - } - lastEvent.mergeEvent(event); - addEvent = false; - } - } - - if (addEvent) { - mEvents.add(event); - } - - // TODO: we need to change when the flush happens so that we don't flush while the - // composing span hasn't changed. But we might need to keep flushing the events for the - // non-editable views and views that don't have the composing state; otherwise some other - // Content Capture features may be delayed. - - final int numberEvents = mEvents.size(); - - final boolean bufferEvent = numberEvents < maxBufferSize; - - if (bufferEvent && !forceFlush) { - final int flushReason; - if (eventType == TYPE_VIEW_TEXT_CHANGED) { - mNextFlushForTextChanged = true; - flushReason = FLUSH_REASON_TEXT_CHANGE_TIMEOUT; - } else { - if (mNextFlushForTextChanged) { - if (sVerbose) { - Log.i(TAG, "Not scheduling flush because next flush is for text changed"); - } - return; - } - - flushReason = FLUSH_REASON_IDLE_TIMEOUT; - } - scheduleFlush(flushReason, /* checkExisting= */ true); - return; - } - - if (mState != STATE_ACTIVE && numberEvents >= maxBufferSize) { - // Callback from startSession hasn't been called yet - typically happens on system - // apps that are started before the system service - // TODO(b/122959591): try to ignore session while system is not ready / boot - // not complete instead. Similarly, the manager service should return right away - // when the user does not have a service set - if (sDebug) { - Log.d(TAG, "Closing session for " + getDebugState() - + " after " + numberEvents + " delayed events"); - } - resetSession(STATE_DISABLED | STATE_NO_RESPONSE); - // TODO(b/111276913): denylist activity / use special flag to indicate that - // when it's launched again - return; - } - final int flushReason; - switch (eventType) { - case ContentCaptureEvent.TYPE_SESSION_STARTED: - flushReason = FLUSH_REASON_SESSION_STARTED; - break; - case ContentCaptureEvent.TYPE_SESSION_FINISHED: - flushReason = FLUSH_REASON_SESSION_FINISHED; - break; - case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING: - flushReason = FLUSH_REASON_VIEW_TREE_APPEARING; - break; - case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED: - flushReason = FLUSH_REASON_VIEW_TREE_APPEARED; - break; - default: - flushReason = forceFlush ? FLUSH_REASON_FORCE_FLUSH : FLUSH_REASON_FULL; - } - - flush(flushReason); - } - - private boolean hasStarted() { - checkOnContentCaptureThread(); - return mState != UNKNOWN_STATE; - } - - private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { - checkOnContentCaptureThread(); - if (sVerbose) { - Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) - + ", checkExisting=" + checkExisting); - } - if (!hasStarted()) { - if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet"); - return; - } - - if (mDisabled.get()) { - // Should not be called on this state, as handleSendEvent checks. - // But we rather add one if check and log than re-schedule and keep the session alive... - Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called " - + "when disabled. events=" + (mEvents == null ? null : mEvents.size())); - return; - } - if (checkExisting && mContentCaptureHandler.hasMessages(MSG_FLUSH)) { - // "Renew" the flush message by removing the previous one - mContentCaptureHandler.removeMessages(MSG_FLUSH); - } - - final int flushFrequencyMs; - if (reason == FLUSH_REASON_TEXT_CHANGE_TIMEOUT) { - flushFrequencyMs = mManager.mOptions.textChangeFlushingFrequencyMs; - } else { - if (reason != FLUSH_REASON_IDLE_TIMEOUT) { - if (sDebug) { - Log.d(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): not a timeout " - + "reason because mDirectServiceInterface is not ready yet"); - } - } - flushFrequencyMs = mManager.mOptions.idleFlushingFrequencyMs; - } - - mNextFlush = System.currentTimeMillis() + flushFrequencyMs; - if (sVerbose) { - Log.v(TAG, "handleScheduleFlush(): scheduled to flush in " - + flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush)); - } - // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage() - mContentCaptureHandler.postDelayed(() -> - flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); - } - - private void flushIfNeeded(@FlushReason int reason) { - checkOnContentCaptureThread(); - if (mEvents == null || mEvents.isEmpty()) { - if (sVerbose) Log.v(TAG, "Nothing to flush"); - return; - } - flush(reason); - } - - /** @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - @Override - public void flush(@FlushReason int reason) { - runOnContentCaptureThread(() -> flushImpl(reason)); - } - - private void flushImpl(@FlushReason int reason) { - checkOnContentCaptureThread(); - if (mEvents == null || mEvents.size() == 0) { - if (sVerbose) { - Log.v(TAG, "Don't flush for empty event buffer."); - } - return; - } - - if (mDisabled.get()) { - Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when " - + "disabled"); - return; - } - - if (!isContentCaptureReceiverEnabled()) { - return; - } - - if (mDirectServiceInterface == null) { - if (sVerbose) { - Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, " - + "client not ready: " + mEvents); - } - if (!mContentCaptureHandler.hasMessages(MSG_FLUSH)) { - scheduleFlush(reason, /* checkExisting= */ false); - } - return; - } - - mNextFlushForTextChanged = false; - - final int numberEvents = mEvents.size(); - final String reasonString = getFlushReasonAsString(reason); - - if (sVerbose) { - ContentCaptureEvent event = mEvents.get(numberEvents - 1); - String forceString = (reason == FLUSH_REASON_FORCE_FLUSH) ? ". The force flush event " - + ContentCaptureEvent.getTypeAsString(event.getType()) : ""; - Log.v(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason) - + forceString); - } - if (mFlushHistory != null) { - // Logs reason, size, max size, idle timeout - final String logRecord = "r=" + reasonString + " s=" + numberEvents - + " m=" + mManager.mOptions.maxBufferSize - + " i=" + mManager.mOptions.idleFlushingFrequencyMs; - mFlushHistory.log(logRecord); - } - try { - mContentCaptureHandler.removeMessages(MSG_FLUSH); - - final ParceledListSlice<ContentCaptureEvent> events = clearEvents(); - mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions); - } catch (RemoteException e) { - Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState() - + ": " + e); - } - } - - @Override - public void updateContentCaptureContext(@Nullable ContentCaptureContext context) { - internalNotifyContextUpdated(mId, context); - } - - /** - * Resets the buffer and return a {@link ParceledListSlice} with the previous events. - */ - @NonNull - private ParceledListSlice<ContentCaptureEvent> clearEvents() { - checkOnContentCaptureThread(); - // NOTE: we must save a reference to the current mEvents and then set it to to null, - // otherwise clearing it would clear it in the receiving side if the service is also local. - if (mEvents == null) { - return new ParceledListSlice<>(Collections.EMPTY_LIST); - } - - final List<ContentCaptureEvent> events = new ArrayList<>(mEvents); - mEvents.clear(); - return new ParceledListSlice<>(events); - } - - /** hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public void destroySession() { - checkOnContentCaptureThread(); - if (sDebug) { - Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " - + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " - + getDebugState()); - } - - reportWrongThreadMetric(); - try { - mSystemServerInterface.finishSession(mId); - } catch (RemoteException e) { - Log.e(TAG, "Error destroying system-service session " + mId + " for " - + getDebugState() + ": " + e); - } - - if (mDirectServiceInterface != null) { - mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0); - } - mDirectServiceInterface = null; - mContentProtectionEventProcessor = null; - mEventProcessQueue.clear(); - } - - // TODO(b/122454205): once we support multiple sessions, we might need to move some of these - // clearings out. - /** @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public void resetSession(int newState) { - checkOnContentCaptureThread(); - if (sVerbose) { - Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " - + getStateAsString(mState) + " to " + getStateAsString(newState)); - } - mState = newState; - mDisabled.set((newState & STATE_DISABLED) != 0); - // TODO(b/122454205): must reset children (which currently is owned by superclass) - mApplicationToken = null; - mShareableActivityToken = null; - mComponentName = null; - mEvents = null; - if (mDirectServiceInterface != null) { - try { - mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "IContentCaptureDirectManager does not exist"); - } - } - mDirectServiceInterface = null; - mContentProtectionEventProcessor = null; - mContentCaptureHandler.removeMessages(MSG_FLUSH); - } - - @Override - void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { - final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) - .setViewNode(node.mNode); - enqueueEvent(event); - } - - @Override - void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) { - final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED) - .setAutofillId(id); - enqueueEvent(event); - } - - @Override - void internalNotifyViewTextChanged( - int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { - // Since the same CharSequence instance may be reused in the TextView, we need to make - // a copy of its content so that its value will not be changed by subsequent updates - // in the TextView. - CharSequence trimmed = TextUtils.trimToParcelableSize(text); - final CharSequence eventText = trimmed != null && trimmed == text - ? trimmed.toString() - : trimmed; - - final int composingStart; - final int composingEnd; - if (text instanceof Spannable) { - composingStart = BaseInputConnection.getComposingSpanStart((Spannable) text); - composingEnd = BaseInputConnection.getComposingSpanEnd((Spannable) text); - } else { - composingStart = ContentCaptureEvent.MAX_INVALID_VALUE; - composingEnd = ContentCaptureEvent.MAX_INVALID_VALUE; - } - - final int startIndex = Selection.getSelectionStart(text); - final int endIndex = Selection.getSelectionEnd(text); - - final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) - .setAutofillId(id).setText(eventText) - .setComposingIndex(composingStart, composingEnd) - .setSelectionIndex(startIndex, endIndex); - enqueueEvent(event); - } - - @Override - void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { - final ContentCaptureEvent event = - new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) - .setInsets(viewInsets); - enqueueEvent(event); - } - - @Override - public void internalNotifyViewTreeEvent(int sessionId, boolean started) { - final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; - final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled(); - final boolean forceFlush = disableFlush ? !started : FORCE_FLUSH; - - final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type); - enqueueEvent(event, forceFlush); - } - - @Override - public void internalNotifySessionResumed() { - final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_RESUMED); - enqueueEvent(event, FORCE_FLUSH); - } - - @Override - public void internalNotifySessionPaused() { - final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_PAUSED); - enqueueEvent(event, FORCE_FLUSH); - } - - @Override - boolean isContentCaptureEnabled() { - return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled(); - } - - // Called by ContentCaptureManager.isContentCaptureEnabled - boolean isDisabled() { - return mDisabled.get(); - } - - /** - * Sets the disabled state of content capture. - * - * @return whether disabled state was changed. - */ - boolean setDisabled(boolean disabled) { - return mDisabled.compareAndSet(!disabled, disabled); - } - - @Override - void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, - @NonNull ContentCaptureContext clientContext) { - final ContentCaptureEvent event = - new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) - .setParentSessionId(parentSessionId) - .setClientContext(clientContext); - enqueueEvent(event, FORCE_FLUSH); - } - - @Override - void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) { - final ContentCaptureEvent event = - new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) - .setParentSessionId(parentSessionId); - enqueueEvent(event, FORCE_FLUSH); - } - - @Override - void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { - final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) - .setClientContext(context); - enqueueEvent(event, FORCE_FLUSH); - } - - @Override - public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { - final ContentCaptureEvent event = - new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) - .setBounds(bounds); - enqueueEvent(event); - } - - private List<ContentCaptureEvent> clearBufferEvents() { - final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>(); - ContentCaptureEvent event; - while ((event = mEventProcessQueue.poll()) != null) { - bufferEvents.add(event); - } - return bufferEvents; - } - - private void enqueueEvent(@NonNull final ContentCaptureEvent event) { - enqueueEvent(event, /* forceFlush */ false); - } - - /** - * Enqueue the event into {@code mEventProcessBuffer} if it is not an urgent request. Otherwise, - * clear the buffer events then starting sending out current event. - */ - private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) { - if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) { - // The buffer events are cleared in the same thread first to prevent new events - // being added during the time of context switch. This would disrupt the sequence - // of events. - final List<ContentCaptureEvent> batchEvents = clearBufferEvents(); - runOnContentCaptureThread(() -> { - for (int i = 0; i < batchEvents.size(); i++) { - sendEvent(batchEvents.get(i)); - } - sendEvent(event, /* forceFlush= */ true); - }); - } else { - mEventProcessQueue.offer(event); - } - } - - @Override - public void notifyContentCaptureEvents( - @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { - runOnUiThread(() -> { - prepareViewStructures(contentCaptureEvents); - runOnContentCaptureThread(() -> - notifyContentCaptureEventsImpl(contentCaptureEvents)); - }); - } - - /** - * Traverse events and pre-process {@link View} events to {@link ViewStructureSession} events. - * If a {@link View} event is invalid, an empty {@link ViewStructureSession} will still be - * provided. - */ - private void prepareViewStructures( - @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { - for (int i = 0; i < contentCaptureEvents.size(); i++) { - int sessionId = contentCaptureEvents.keyAt(i); - ArrayList<Object> events = contentCaptureEvents.valueAt(i); - for_each_event: for (int j = 0; j < events.size(); j++) { - Object event = events.get(j); - if (event instanceof View) { - View view = (View) event; - ContentCaptureSession session = view.getContentCaptureSession(); - ViewStructureSession structureSession = new ViewStructureSession(); - - // Replace the View event with ViewStructureSession no matter the data is - // available or not. This is to ensure the sequence of the events are still - // the same. Calls to notifyViewAppeared will check the availability later. - events.set(j, structureSession); - if (session == null) { - Log.w(TAG, "no content capture session on view: " + view); - continue for_each_event; - } - int actualId = session.getId(); - if (actualId != sessionId) { - Log.w(TAG, "content capture session mismatch for view (" + view - + "): was " + sessionId + " before, it's " + actualId + " now"); - continue for_each_event; - } - ViewStructure structure = session.newViewStructure(view); - view.onProvideContentCaptureStructure(structure, /* flags= */ 0); - - structureSession.setSession(session); - structureSession.setStructure(structure); - } - } - } - } - - private void notifyContentCaptureEventsImpl( - @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { - checkOnContentCaptureThread(); - try { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); - } - for (int i = 0; i < contentCaptureEvents.size(); i++) { - int sessionId = contentCaptureEvents.keyAt(i); - internalNotifyViewTreeEvent(sessionId, /* started= */ true); - ArrayList<Object> events = contentCaptureEvents.valueAt(i); - for_each_event: for (int j = 0; j < events.size(); j++) { - Object event = events.get(j); - if (event instanceof AutofillId) { - internalNotifyViewDisappeared(sessionId, (AutofillId) event); - } else if (event instanceof ViewStructureSession viewStructureSession) { - viewStructureSession.notifyViewAppeared(); - } else if (event instanceof Insets) { - internalNotifyViewInsetsChanged(sessionId, (Insets) event); - } else { - Log.w(TAG, "invalid content capture event: " + event); - } - } - internalNotifyViewTreeEvent(sessionId, /* started= */ false); - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - } - - @Override - void dump(@NonNull String prefix, @NonNull PrintWriter pw) { - super.dump(prefix, pw); - - pw.print(prefix); pw.print("mContext: "); pw.println(mContext); - pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId()); - if (mDirectServiceInterface != null) { - pw.print(prefix); pw.print("mDirectServiceInterface: "); - pw.println(mDirectServiceInterface); - } - pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get()); - pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled()); - pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState)); - if (mApplicationToken != null) { - pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken); - } - if (mShareableActivityToken != null) { - pw.print(prefix); pw.print("sharable activity token: "); - pw.println(mShareableActivityToken); - } - if (mComponentName != null) { - pw.print(prefix); pw.print("component name: "); - pw.println(mComponentName.flattenToShortString()); - } - if (mEvents != null && !mEvents.isEmpty()) { - final int numberEvents = mEvents.size(); - pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents); - pw.print('/'); pw.println(mManager.mOptions.maxBufferSize); - if (sVerbose && numberEvents > 0) { - final String prefix3 = prefix + " "; - for (int i = 0; i < numberEvents; i++) { - final ContentCaptureEvent event = mEvents.get(i); - pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw); - pw.println(); - } - } - pw.print(prefix); pw.print("mNextFlushForTextChanged: "); - pw.println(mNextFlushForTextChanged); - pw.print(prefix); pw.print("flush frequency: "); - if (mNextFlushForTextChanged) { - pw.println(mManager.mOptions.textChangeFlushingFrequencyMs); - } else { - pw.println(mManager.mOptions.idleFlushingFrequencyMs); - } - pw.print(prefix); pw.print("next flush: "); - TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw); - pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")"); - } - if (mFlushHistory != null) { - pw.print(prefix); pw.println("flush history:"); - mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println(); - } else { - pw.print(prefix); pw.println("not logging flush history"); - } - - super.dump(prefix, pw); - } - - /** - * Gets a string that can be used to identify the activity on logging statements. - */ - private String getActivityName() { - return mComponentName == null - ? "pkg:" + mContext.getPackageName() - : "act:" + mComponentName.flattenToShortString(); - } - - @NonNull - private String getDebugState() { - return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled=" - + mDisabled.get() + "]"; - } - - @NonNull - private String getDebugState(@FlushReason int reason) { - return getDebugState() + ", reason=" + getFlushReasonAsString(reason); - } - - private boolean isContentProtectionReceiverEnabled() { - return mManager.mOptions.contentProtectionOptions.enableReceiver; - } - - private boolean isContentCaptureReceiverEnabled() { - return mManager.mOptions.enableReceiver; - } - - private boolean isContentProtectionEnabled() { - // Should not be possible for mComponentName to be null here but check anyway - // Should not be possible for groups to be empty if receiver is enabled but check anyway - return mManager.mOptions.contentProtectionOptions.enableReceiver - && mManager.getContentProtectionEventBuffer() != null - && mComponentName != null - && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() - || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); - } - - /** - * Checks that the current work is running on the assigned thread from {@code mHandler} and - * count the number of times running on the wrong thread. - * - * <p>It is not guaranteed that the callers always invoke function from a single thread. - * Therefore, accessing internal properties in {@link MainContentCaptureSession} should - * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> - */ - private void checkOnContentCaptureThread() { - final boolean onContentCaptureThread = mContentCaptureHandler.getLooper().isCurrentThread(); - if (!onContentCaptureThread) { - mWrongThreadCount.incrementAndGet(); - Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread()); - } - } - - /** Reports number of times running on the wrong thread. */ - private void reportWrongThreadMetric() { - Counter.logIncrement( - CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID, mWrongThreadCount.getAndSet(0)); - } - - /** - * Ensures that {@code r} will be running on the assigned thread. - * - * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable. - * </p> - */ - private void runOnContentCaptureThread(@NonNull Runnable r) { - if (!mContentCaptureHandler.getLooper().isCurrentThread()) { - mContentCaptureHandler.post(r); - } else { - r.run(); - } - } - - private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { - if (!mContentCaptureHandler.getLooper().isCurrentThread()) { - mContentCaptureHandler.removeMessages(what); - mContentCaptureHandler.post(r); - } else { - r.run(); - } - } - - private void runOnUiThread(@NonNull Runnable r) { - if (mUiHandler.getLooper().isCurrentThread()) { - r.run(); - } else { - mUiHandler.post(r); - } - } - - /** - * Holds {@link ContentCaptureSession} and related {@link ViewStructure} for processing. - */ - private static final class ViewStructureSession { - @Nullable private ContentCaptureSession mSession; - @Nullable private ViewStructure mStructure; - - ViewStructureSession() {} - - void setSession(@Nullable ContentCaptureSession session) { - this.mSession = session; - } - - void setStructure(@Nullable ViewStructure struct) { - this.mStructure = struct; - } - - /** - * Calls {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)} if the session and - * the view structure are available. - */ - void notifyViewAppeared() { - if (mSession != null && mStructure != null) { - mSession.notifyViewAppeared(mStructure); - } - } - } -} diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 3c5623f380f5..9896d981c9b3 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -17,8 +17,10 @@ package android.widget; import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL; +import static android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO; import static android.appwidget.flags.Flags.drawDataParcel; import static android.appwidget.flags.Flags.remoteAdapterConversion; +import static android.util.proto.ProtoInputStream.NO_MORE_FIELDS; import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; import android.annotation.AttrRes; @@ -94,6 +96,9 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TypedValue; import android.util.TypedValue.ComplexDimensionUnit; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.LayoutInflater.Filter; @@ -7822,4 +7827,278 @@ public class RemoteViews implements Parcelable, Filter { mClassCookies = classCookies; } } + + /** + * Write this RemoteViews to proto. + * @hide + */ + @FlaggedApi(FLAG_REMOTE_VIEWS_PROTO) + public void writePreviewToProto(@NonNull Context context, @NonNull ProtoOutputStream out) { + if (mApplication != null) { + // mApplication may be null if this was created with DrawInstructions constructor. + out.write(RemoteViewsProto.PACKAGE_NAME, mApplication.packageName); + } + Resources appResources = getContextForResourcesEnsuringCorrectCachedApkPaths( + context).getResources(); + if (mLayoutId != 0) { + out.write(RemoteViewsProto.LAYOUT_ID, appResources.getResourceName(mLayoutId)); + } + if (mLightBackgroundLayoutId != 0) { + out.write(RemoteViewsProto.LIGHT_BACKGROUND_LAYOUT_ID, + appResources.getResourceName(mLightBackgroundLayoutId)); + } + if (mViewId != 0 && mViewId != -1) { + out.write(RemoteViewsProto.VIEW_ID, appResources.getResourceName(mViewId)); + } + out.write(RemoteViewsProto.IS_ROOT, mIsRoot); + out.write(RemoteViewsProto.APPLY_FLAGS, mApplyFlags); + out.write(RemoteViewsProto.HAS_DRAW_INSTRUCTIONS, mHasDrawInstructions); + if (mProviderInstanceId != -1) { + out.write(RemoteViewsProto.PROVIDER_INSTANCE_ID, mProviderInstanceId); + } + + if (!hasMultipleLayouts()) { + out.write(RemoteViewsProto.MODE, MODE_NORMAL); + if (mIdealSize != null) { + final long token = out.start(RemoteViewsProto.IDEAL_SIZE); + out.write(SizeFProto.WIDTH, mIdealSize.getWidth()); + out.write(SizeFProto.HEIGHT, mIdealSize.getHeight()); + out.end(token); + } + } else if (hasSizedRemoteViews()) { + out.write(RemoteViewsProto.MODE, MODE_HAS_SIZED_REMOTEVIEWS); + for (RemoteViews view : mSizedRemoteViews) { + final long sizedViewToken = out.start(RemoteViewsProto.SIZED_REMOTEVIEWS); + view.writePreviewToProto(context, out); + out.end(sizedViewToken); + } + } else { + out.write(RemoteViewsProto.MODE, MODE_HAS_LANDSCAPE_AND_PORTRAIT); + final long landscapeViewToken = out.start(RemoteViewsProto.LANDSCAPE_REMOTEVIEWS); + mLandscape.writePreviewToProto(context, out); + out.end(landscapeViewToken); + final long portraitViewToken = out.start(RemoteViewsProto.PORTRAIT_REMOTEVIEWS); + mPortrait.writePreviewToProto(context, out); + out.end(portraitViewToken); + } + } + + /** + * Create a RemoteViews from proto input. + * @hide + */ + @FlaggedApi(FLAG_REMOTE_VIEWS_PROTO) + public static RemoteViews createPreviewFromProto(Context context, ProtoInputStream in) + throws Exception { + return createFromProto(in).create(context, context.getResources(), /* rootData= */ null, + /* depth= */ 0); + } + + private static PendingResources<RemoteViews> createFromProto(ProtoInputStream in) + throws Exception { + // Grouping these variables into an anonymous object allows us to access them through `ref` + // (which is final) later in the lambda. + final var ref = new Object() { + final RemoteViews mRv = new RemoteViews(); + int mMode = 0; + int mApplyFlags = 0; + long mProviderInstanceId = -1; + String mPackageName = null; + SizeF mIdealSize = null; + String mLayoutResName = null; + String mLightBackgroundResName = null; + String mViewResName = null; + final List<PendingResources<RemoteViews>> mSizedRemoteViews = new ArrayList<>(); + PendingResources<RemoteViews> mLandscapeViews = null; + PendingResources<RemoteViews> mPortraitViews = null; + boolean mIsRoot = false; + boolean mHasDrawInstructions = false; + }; + + try { + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.MODE: + ref.mMode = in.readInt(RemoteViewsProto.MODE); + break; + case (int) RemoteViewsProto.PACKAGE_NAME: + ref.mPackageName = in.readString(RemoteViewsProto.PACKAGE_NAME); + break; + case (int) RemoteViewsProto.IDEAL_SIZE: + final long idealSizeToken = in.start(RemoteViewsProto.IDEAL_SIZE); + ref.mIdealSize = createSizeFFromProto(in); + in.end(idealSizeToken); + break; + case (int) RemoteViewsProto.LAYOUT_ID: + ref.mLayoutResName = in.readString(RemoteViewsProto.LAYOUT_ID); + break; + case (int) RemoteViewsProto.LIGHT_BACKGROUND_LAYOUT_ID: + ref.mLightBackgroundResName = in.readString( + RemoteViewsProto.LIGHT_BACKGROUND_LAYOUT_ID); + break; + case (int) RemoteViewsProto.VIEW_ID: + ref.mViewResName = in.readString(RemoteViewsProto.VIEW_ID); + break; + case (int) RemoteViewsProto.APPLY_FLAGS: + ref.mApplyFlags = in.readInt(RemoteViewsProto.APPLY_FLAGS); + break; + case (int) RemoteViewsProto.PROVIDER_INSTANCE_ID: + ref.mProviderInstanceId = in.readInt(RemoteViewsProto.PROVIDER_INSTANCE_ID); + break; + case (int) RemoteViewsProto.SIZED_REMOTEVIEWS: + final long sizedToken = in.start(RemoteViewsProto.SIZED_REMOTEVIEWS); + ref.mSizedRemoteViews.add(createFromProto(in)); + in.end(sizedToken); + break; + case (int) RemoteViewsProto.LANDSCAPE_REMOTEVIEWS: + final long landscapeToken = in.start( + RemoteViewsProto.LANDSCAPE_REMOTEVIEWS); + ref.mLandscapeViews = createFromProto(in); + in.end(landscapeToken); + break; + case (int) RemoteViewsProto.PORTRAIT_REMOTEVIEWS: + final long portraitToken = in.start(RemoteViewsProto.PORTRAIT_REMOTEVIEWS); + ref.mPortraitViews = createFromProto(in); + in.end(portraitToken); + break; + case (int) RemoteViewsProto.IS_ROOT: + ref.mIsRoot = in.readBoolean(RemoteViewsProto.IS_ROOT); + break; + case (int) RemoteViewsProto.HAS_DRAW_INSTRUCTIONS: + ref.mHasDrawInstructions = in.readBoolean( + RemoteViewsProto.HAS_DRAW_INSTRUCTIONS); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return (context, resources, rootData, depth) -> { + if (depth > MAX_NESTED_VIEWS && (UserHandle.getAppId(Binder.getCallingUid()) + != Process.SYSTEM_UID)) { + throw new IllegalArgumentException("Too many nested views."); + } + depth++; + + RemoteViews rv = ref.mRv; + rv.mApplyFlags = ref.mApplyFlags; + rv.mIsRoot = ref.mIsRoot; + rv.mHasDrawInstructions = ref.mHasDrawInstructions; + + // The root view will read its HierarchyRootData (bitmap cache, collection cache) from + // proto; all nested views will instead get it through the rootData parameter. + if (rootData == null) { + if (!rv.mIsRoot || depth != 1) { + throw new IllegalStateException( + "A nested view did not receive HierarchyRootData"); + } + rootData = rv.getHierarchyRootData(); + } else { + rv.configureAsChild(rootData); + } + + Context appContext = null; + Resources appResources = null; + if (!ref.mHasDrawInstructions) { + checkProtoResultNotNull(ref.mPackageName, "No application info"); + rv.mApplication = context.getPackageManager().getApplicationInfo(ref.mPackageName, + /* flags= */ 0); + appContext = rv.getContextForResourcesEnsuringCorrectCachedApkPaths(context); + appResources = appContext.getResources(); + + checkProtoResultNotNull(ref.mLayoutResName, "No layout id"); + rv.mLayoutId = appResources.getIdentifier(ref.mLayoutResName, /* defType= */ null, + /* defPackage= */ null); + checkValidResource(rv.mLayoutId, "Invalid layout id", ref.mLayoutResName); + + if (ref.mViewResName != null) { + rv.mViewId = appResources.getIdentifier(ref.mViewResName, /* defType= */ null, + /* defPackage= */ null); + checkValidResource(rv.mViewId, "Invalid view id", ref.mViewResName); + } + + if (ref.mLightBackgroundResName != null) { + int lightBackgroundLayoutId = appResources.getIdentifier( + ref.mLightBackgroundResName, + /* defType= */ null, /* defPackage= */ null); + checkValidResource(lightBackgroundLayoutId, + "Invalid light background layout id", ref.mLightBackgroundResName); + rv.setLightBackgroundLayoutId(lightBackgroundLayoutId); + } + } + if (ref.mProviderInstanceId != -1) { + rv.mProviderInstanceId = ref.mProviderInstanceId; + } + if (ref.mMode == MODE_NORMAL) { + rv.setIdealSize(ref.mIdealSize); + return rv; + } else if (ref.mMode == MODE_HAS_SIZED_REMOTEVIEWS) { + List<RemoteViews> sizedViews = new ArrayList<>(); + for (RemoteViews.PendingResources<RemoteViews> pendingViews : + ref.mSizedRemoteViews) { + RemoteViews views = pendingViews.create(context, resources, rootData, depth); + sizedViews.add(views); + } + rv.initializeSizedRemoteViews(sizedViews.iterator()); + return rv; + } else if (ref.mMode == MODE_HAS_LANDSCAPE_AND_PORTRAIT) { + checkProtoResultNotNull(ref.mLandscapeViews, "Missing landscape views"); + checkProtoResultNotNull(ref.mPortraitViews, "Missing portrait views"); + RemoteViews parentRv = new RemoteViews( + ref.mLandscapeViews.create(context, resources, rootData, depth), + ref.mPortraitViews.create(context, resources, rootData, depth)); + parentRv.initializeFrom(/* src= */ rv, /* hierarchyRoot= */ rv); + return parentRv; + } else { + throw new InvalidProtoException(ref.mMode + " is not a valid mode."); + } + }; + } + + private static class InvalidProtoException extends Exception { + InvalidProtoException(String message) { + super(message); + } + } + + private interface PendingResources<T> { + T create(Context context, Resources appResources, HierarchyRootData rootData, int depth) + throws Exception; + } + + private static void checkValidResource(int id, String message, String resName) + throws Exception { + if (id == 0) throw new Exception(message + ": " + resName); + } + + private static void checkProtoResultNotNull(Object o, String message) + throws InvalidProtoException { + if (o == null) { + throw new InvalidProtoException(message); + } + } + + private static SizeF createSizeFFromProto(ProtoInputStream in) throws Exception { + float width = 0; + float height = 0; + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) SizeFProto.WIDTH: + width = in.readFloat(SizeFProto.WIDTH); + break; + case (int) SizeFProto.HEIGHT: + height = in.readFloat(SizeFProto.HEIGHT); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading SizeF proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + + return new SizeF(width, height); + } } diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig index 503e542f715a..56a2cf72a075 100644 --- a/core/java/android/widget/flags/notification_widget_flags.aconfig +++ b/core/java/android/widget/flags/notification_widget_flags.aconfig @@ -57,3 +57,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "conversation_layout_use_maximum_child_height" + namespace: "systemui" + description: "MessagingChild always needs to be measured during MessagingLinearLayout onMeasure." + bug: "324537506" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index d5398e6268dc..781a9019d1ae 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -16,6 +16,8 @@ package android.window; +import static android.util.SequenceUtils.getInitSeq; + import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; @@ -53,6 +55,9 @@ public class ClientWindowFrames implements Parcelable { public float compatScale = 1f; + /** To make sure the info update between client and system server is in order. */ + public int seq = getInitSeq(); + public ClientWindowFrames() { } @@ -74,6 +79,7 @@ public class ClientWindowFrames implements Parcelable { } isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout; compatScale = other.compatScale; + seq = other.seq; } /** Needed for AIDL out parameters. */ @@ -84,6 +90,7 @@ public class ClientWindowFrames implements Parcelable { attachedFrame = in.readTypedObject(Rect.CREATOR); isParentFrameClippedByDisplayCutout = in.readBoolean(); compatScale = in.readFloat(); + seq = in.readInt(); } @Override @@ -94,6 +101,7 @@ public class ClientWindowFrames implements Parcelable { dest.writeTypedObject(attachedFrame, flags); dest.writeBoolean(isParentFrameClippedByDisplayCutout); dest.writeFloat(compatScale); + dest.writeInt(seq); } @Override @@ -116,6 +124,7 @@ public class ClientWindowFrames implements Parcelable { return false; } final ClientWindowFrames other = (ClientWindowFrames) o; + // seq is for internal bookkeeping only. return frame.equals(other.frame) && displayFrame.equals(other.displayFrame) && parentFrame.equals(other.parentFrame) diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 8bd39fbe3a52..8ded608b71e1 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -663,6 +663,7 @@ public final class TransitionInfo implements Parcelable { private final Rect mStartAbsBounds = new Rect(); private final Rect mEndAbsBounds = new Rect(); private final Point mEndRelOffset = new Point(); + private final Point mEndParentSize = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; private boolean mAllowEnterPip; private int mStartDisplayId = INVALID_DISPLAY; @@ -697,6 +698,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.readFromParcel(in); mEndAbsBounds.readFromParcel(in); mEndRelOffset.readFromParcel(in); + mEndParentSize.readFromParcel(in); mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); mAllowEnterPip = in.readBoolean(); mStartDisplayId = in.readInt(); @@ -721,6 +723,7 @@ public final class TransitionInfo implements Parcelable { out.mStartAbsBounds.set(mStartAbsBounds); out.mEndAbsBounds.set(mEndAbsBounds); out.mEndRelOffset.set(mEndRelOffset); + out.mEndParentSize.set(mEndParentSize); out.mTaskInfo = mTaskInfo; out.mAllowEnterPip = mAllowEnterPip; out.mStartDisplayId = mStartDisplayId; @@ -781,6 +784,13 @@ public final class TransitionInfo implements Parcelable { } /** + * Sets the size of its parent container after the change. + */ + public void setEndParentSize(int width, int height) { + mEndParentSize.set(width, height); + } + + /** * Sets the taskinfo of this container if this is a task. WARNING: this takes the * reference, so don't modify it afterwards. */ @@ -916,6 +926,14 @@ public final class TransitionInfo implements Parcelable { return mEndRelOffset; } + /** + * Returns the size of parent container after the change. + */ + @NonNull + public Point getEndParentSize() { + return mEndParentSize; + } + /** @return the leash or surface to animate for this container */ @NonNull public SurfaceControl getLeash() { @@ -1003,6 +1021,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.writeToParcel(dest, flags); mEndAbsBounds.writeToParcel(dest, flags); mEndRelOffset.writeToParcel(dest, flags); + mEndParentSize.writeToParcel(dest, flags); dest.writeTypedObject(mTaskInfo, flags); dest.writeBoolean(mAllowEnterPip); dest.writeInt(mStartDisplayId); @@ -1055,6 +1074,9 @@ public final class TransitionInfo implements Parcelable { if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) { sb.append(" eo="); sb.append(mEndRelOffset); } + if (!mEndParentSize.equals(0, 0)) { + sb.append(" epz=").append(mEndParentSize); + } sb.append(" d="); if (mStartDisplayId != mEndDisplayId) { sb.append(mStartDisplayId).append("->"); diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 4ca64e73ad7c..4fb6e698d592 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -37,6 +37,7 @@ import android.view.IWindow; import android.view.IWindowSession; import android.view.ImeBackAnimationController; import android.view.MotionEvent; +import android.view.ViewRootImpl; import androidx.annotation.VisibleForTesting; @@ -49,6 +50,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; import java.util.TreeMap; +import java.util.function.BooleanSupplier; import java.util.function.Supplier; /** @@ -68,6 +70,7 @@ import java.util.function.Supplier; public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private IWindowSession mWindowSession; private IWindow mWindow; + private ViewRootImpl mViewRoot; @VisibleForTesting public final BackTouchTracker mTouchTracker = new BackTouchTracker(); @VisibleForTesting @@ -134,10 +137,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { * is attached a window. */ public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window, + @Nullable ViewRootImpl viewRoot, @Nullable ImeBackAnimationController imeBackAnimationController) { synchronized (mLock) { mWindowSession = windowSession; mWindow = window; + mViewRoot = viewRoot; mImeBackAnimationController = imeBackAnimationController; if (!mAllCallbacks.isEmpty()) { setTopOnBackInvokedCallback(getTopCallback()); @@ -151,6 +156,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { clear(); mWindow = null; mWindowSession = null; + mViewRoot = null; mImeBackAnimationController = null; } } @@ -176,8 +182,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return; } if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) { - // Fall back to compat back key injection if legacy back behaviour should be used. - if (!isOnBackInvokedCallbackEnabled()) return; if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback && mImeBackAnimationController != null) { // register ImeBackAnimationController instead to play predictive back animation @@ -300,6 +304,14 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } + private boolean callOnKeyPreIme() { + if (mViewRoot != null && !isOnBackInvokedCallbackEnabled(mViewRoot.mContext)) { + return mViewRoot.injectBackKeyEvents(/*preImeOnly*/ true); + } else { + return false; + } + } + private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) { if (mWindowSession == null || mWindow == null) { return; @@ -308,8 +320,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { OnBackInvokedCallbackInfo callbackInfo = null; if (callback != null) { int priority = mAllCallbacks.get(callback); - final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper( - callback, mTouchTracker, mProgressAnimator, mHandler); + final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback, + mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme); callbackInfo = new OnBackInvokedCallbackInfo( iCallback, priority, @@ -399,16 +411,20 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final BackTouchTracker mTouchTracker; @NonNull private final Handler mHandler; + @NonNull + private final BooleanSupplier mOnKeyPreIme; OnBackInvokedCallbackWrapper( @NonNull OnBackInvokedCallback callback, @NonNull BackTouchTracker touchTracker, @NonNull BackProgressAnimator progressAnimator, - @NonNull Handler handler) { + @NonNull Handler handler, + @NonNull BooleanSupplier onKeyPreIme) { mCallback = new WeakReference<>(callback); mTouchTracker = touchTracker; mProgressAnimator = progressAnimator; mHandler = handler; + mOnKeyPreIme = onKeyPreIme; } @Override @@ -460,6 +476,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void onBackInvoked() throws RemoteException { mHandler.post(() -> { mTouchTracker.reset(); + if (consumedByOnKeyPreIme()) return; boolean isInProgress = mProgressAnimator.isBackAnimationInProgress(); final OnBackInvokedCallback callback = mCallback.get(); if (callback == null) { @@ -481,6 +498,30 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { }); } + private boolean consumedByOnKeyPreIme() { + final OnBackInvokedCallback callback = mCallback.get(); + if (callback instanceof ImeBackAnimationController + || callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) { + // call onKeyPreIme API if the current callback is an IME callback and the app has + // not set enableOnBackInvokedCallback="false" + try { + boolean consumed = mOnKeyPreIme.getAsBoolean(); + if (consumed) { + // back event intercepted by app in onKeyPreIme -> cancel the IME animation. + final OnBackAnimationCallback animationCallback = + getBackAnimationCallback(); + if (animationCallback != null) { + mProgressAnimator.onBackCancelled(animationCallback::onBackCancelled); + } + return true; + } + } catch (Exception e) { + Log.d(TAG, "Failed to call onKeyPreIme", e); + } + } + return false; + } + @Override public void setTriggerBack(boolean triggerBack) throws RemoteException { mTouchTracker.setTriggerBack(triggerBack); diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index cc54a93a6018..15adc80add01 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -19,8 +19,6 @@ import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; -import static com.android.window.flags.Flags.windowTokenConfigThreadSafe; - import android.annotation.AnyThread; import android.annotation.MainThread; import android.annotation.NonNull; @@ -146,7 +144,7 @@ public class WindowTokenClient extends Binder { if (context == null) { return; } - if (shouldReportConfigChange && windowTokenConfigThreadSafe()) { + if (shouldReportConfigChange) { // Only report to ClientTransactionListenerController when shouldReportConfigChange. final ClientTransactionListenerController controller = getClientTransactionListenerController(); diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index ca125dafc2f7..d0ab674a0e62 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -120,3 +120,10 @@ flag { description: "Whether to enable min/max window size constraints when resizing a window in desktop windowing mode" bug: "327589741" } + +flag { + name: "show_desktop_windowing_dev_option" + namespace: "lse_desktop_experience" + description: "Whether to show developer option for enabling desktop windowing mode" + bug: "348193756" +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index b71468247e37..985dc102b31b 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -192,4 +192,15 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "ensure_wallpaper_in_transitions" + namespace: "windowing_frontend" + description: "Ensure that wallpaper window tokens are always present/available for collection in transitions" + bug: "347593088" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 0a4762d91610..8cd2a3ed124e 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -93,16 +93,6 @@ flag { flag { namespace: "windowing_sdk" - name: "window_token_config_thread_safe" - description: "Ensure the Configuration pre/post changed is thread safe" - bug: "334285008" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - namespace: "windowing_sdk" name: "always_defer_transition_when_apply_wct" description: "Report error when defer transition fails when it should not" bug: "335562144" @@ -113,17 +103,6 @@ flag { flag { namespace: "windowing_sdk" - name: "window_session_relayout_info" - description: "Pass an out RelayoutInfo instead of Bundle to fix the Parcel recycle bug" - bug: "335601427" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - namespace: "windowing_sdk" name: "fix_pip_restore_to_overlay" description: "Restore exit-pip activity back to ActivityEmbedding overlay" bug: "297887697" @@ -191,3 +170,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "windowing_sdk" + name: "fix_no_container_update_without_resize" + description: "Fix the containers not being updated when the Task is brought to front and has the same configuration" + bug: "344721335" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java index fc3cd45787f7..c8d619481726 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java @@ -29,8 +29,6 @@ import android.app.Activity; import android.content.ComponentName; import android.os.Bundle; import android.provider.Settings; -import android.text.TextUtils; -import android.view.View; import android.view.accessibility.AccessibilityManager; import android.widget.GridView; import android.widget.TextView; @@ -73,16 +71,11 @@ public class AccessibilityButtonChooserActivity extends Activity { promptPrologue.setText(isTouchExploreOn ? R.string.accessibility_gesture_3finger_prompt_text : R.string.accessibility_gesture_prompt_text); - } - if (TextUtils.isEmpty(component)) { final TextView prompt = findViewById(R.id.accessibility_button_prompt); - if (isGestureNavigateEnabled) { - prompt.setText(isTouchExploreOn - ? R.string.accessibility_gesture_3finger_instructional_text - : R.string.accessibility_gesture_instructional_text); - } - prompt.setVisibility(View.VISIBLE); + prompt.setText(isTouchExploreOn + ? R.string.accessibility_gesture_3finger_instructional_text + : R.string.accessibility_gesture_instructional_text); } mTargets.addAll(getTargets(this, SOFTWARE)); diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java index e8472d44b41c..0b1ecf78d28c 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java @@ -126,7 +126,7 @@ public final class AccessibilityUtils { } /** - * Changes an accessibility component's state. + * Changes an accessibility component's state for the calling process userId */ public static void setAccessibilityServiceState(Context context, ComponentName componentName, boolean enabled) { diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java index 5b09a8be8c65..6b0ca9faa2a4 100644 --- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java +++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java @@ -181,6 +181,27 @@ public final class ShortcutUtils { } /** + * Converts {@link Settings.Secure} key to {@link UserShortcutType}. + * + * @param key The shortcut key in Settings. + * @return The mapped type + */ + @UserShortcutType + public static int convertToType(String key) { + return switch (key) { + case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> UserShortcutType.SOFTWARE; + case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> UserShortcutType.QUICK_SETTINGS; + case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> UserShortcutType.HARDWARE; + case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED -> + UserShortcutType.TRIPLETAP; + case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED -> + UserShortcutType.TWOFINGER_DOUBLETAP; + default -> throw new IllegalArgumentException( + "Unsupported user shortcut key: " + key); + }; + } + + /** * Updates an accessibility state if the accessibility service is a Always-On a11y service, * a.k.a. AccessibilityServices that has FLAG_REQUEST_ACCESSIBILITY_BUTTON * <p> @@ -224,7 +245,9 @@ public final class ShortcutUtils { boolean enableA11yService = servicesWithShortcuts.contains(componentName); AccessibilityUtils.setAccessibilityServiceState( context, - ComponentName.unflattenFromString(componentName), enableA11yService); + ComponentName.unflattenFromString(componentName), + enableA11yService, + userId); } } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 920981e2b8fe..a1945352ae09 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1209,9 +1209,19 @@ public class ResolverActivity extends Activity implements if (!isChangingConfigurations() && mPickOptionRequest != null) { mPickOptionRequest.cancel(); } - if (mMultiProfilePagerAdapter != null - && mMultiProfilePagerAdapter.getActiveListAdapter() != null) { - mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy(); + if (mMultiProfilePagerAdapter != null) { + ResolverListAdapter activeAdapter = + mMultiProfilePagerAdapter.getActiveListAdapter(); + if (activeAdapter != null) { + activeAdapter.onDestroy(); + } + if (android.service.chooser.Flags.fixResolverMemoryLeak()) { + ResolverListAdapter inactiveAdapter = + mMultiProfilePagerAdapter.getInactiveListAdapter(); + if (inactiveAdapter != null) { + inactiveAdapter.onDestroy(); + } + } } } diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java index ded142ce49e5..f61157175f9d 100644 --- a/core/java/com/android/internal/compat/ChangeReporter.java +++ b/core/java/com/android/internal/compat/ChangeReporter.java @@ -42,7 +42,7 @@ import java.util.function.Function; * * @hide */ -public final class ChangeReporter { +public class ChangeReporter { private static final String TAG = "CompatChangeReporter"; private static final Function<Integer, Set<ChangeReport>> NEW_CHANGE_REPORT_SET = uid -> Collections.synchronizedSet(new HashSet<>()); @@ -88,17 +88,20 @@ public final class ChangeReporter { * Report the change to stats log and to the debug log if the change was not previously * logged already. * - * @param uid affected by the change - * @param changeId the reported change id - * @param state of the reported change - enabled/disabled/only logged - * @param isLoggableBySdk whether debug logging is allowed for this change based on target - * SDK version. This is combined with other logic to determine whether to - * actually log. If the sdk version does not matter, should be true. + * @param uid affected by the change + * @param changeId the reported change id + * @param state of the reported change - enabled/disabled/only logged + * @param isKnownSystemApp do we know that the affected app is a system app? (if true is + * definitely a system app, if false it may or may not be a system app) + * @param isLoggableBySdk whether debug logging is allowed for this change based on target + * SDK version. This is combined with other logic to determine whether + * to actually log. If the sdk version does not matter, should be true. */ - public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) { + public void reportChange(int uid, long changeId, int state, boolean isKnownSystemApp, + boolean isLoggableBySdk) { boolean isAlreadyReported = checkAndSetIsAlreadyReported(uid, new ChangeReport(changeId, state)); - if (!isAlreadyReported) { + if (shouldWriteToStatsLog(isKnownSystemApp, isAlreadyReported)) { FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId, state, mSource); } @@ -116,7 +119,7 @@ public final class ChangeReporter { * @param state of the reported change - enabled/disabled/only logged */ public void reportChange(int uid, long changeId, int state) { - reportChange(uid, changeId, state, true); + reportChange(uid, changeId, state, false, true); } /** @@ -136,14 +139,15 @@ public final class ChangeReporter { /** * Returns whether the next report should be logged to FrameworkStatsLog. * - * @param uid affected by the change - * @param changeId the reported change id - * @param state of the reported change - enabled/disabled/only logged + * @param isKnownSystemApp do we know that the affected app is a system app? (if true is + * definitely a system app, if false it may or may not be a system app) + * @param isAlreadyReported is the change already reported * @return true if the report should be logged */ @VisibleForTesting - boolean shouldWriteToStatsLog(int uid, long changeId, int state) { - return !isAlreadyReported(uid, new ChangeReport(changeId, state)); + boolean shouldWriteToStatsLog(boolean isKnownSystemApp, boolean isAlreadyReported) { + // We don't log where we know the source is a system app or is already reported + return !isKnownSystemApp && !isAlreadyReported; } /** @@ -224,6 +228,19 @@ public final class ChangeReporter { return mReportedChanges.getOrDefault(uid, EMPTY_SET).contains(report); } + /** + * Returns whether the next report should be logged. + * + * @param uid affected by the change + * @param changeId the reported change id + * @param state of the reported change - enabled/disabled/only logged + * @return true if the report should be logged + */ + @VisibleForTesting + boolean isAlreadyReported(int uid, long changeId, int state) { + return isAlreadyReported(uid, new ChangeReport(changeId, state)); + } + private void markAsReported(int uid, ChangeReport report) { mReportedChanges.computeIfAbsent(uid, NEW_CHANGE_REPORT_SET).add(report); } diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index 6ffa8261c8fd..618f622cb9ce 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -137,9 +137,31 @@ public class Cuj { public static final int CUJ_LAUNCHER_PRIVATE_SPACE_LOCK = 102; public static final int CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK = 103; + /** + * Track maximize window interaction in desktop mode. + * + * <p>Tracking starts onClick of the maximize window button or option {@link + * com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel#onClick} + * and finishes when the window animation is ended {@link + * com.android.wm.shell.windowdecor.ToggleResizeDesktopTaskTransitionHandler#startAnimation} + */ + public static final int CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW = 104; + + /** + * Track fade-in animation when in SystemUI process fold + * + * <p>Tracking starts after the screen turns on and finish when the animation is over {@link + * com.android.systemui.unfold.FoldLightRevealOverlayAnimation#playFoldLightRevealOverlayAnimation} for the span + */ + public static final int CUJ_FOLD_ANIM = 105; + + /** + * Track window re-sizing interaction in desktop mode. + */ + public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting - static final int LAST_CUJ = CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK; + @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_RESIZE_WINDOW; /** @hide */ @IntDef({ @@ -234,11 +256,13 @@ public class Cuj { CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK, CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK, CUJ_LAUNCHER_PRIVATE_SPACE_LOCK, - CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK + CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK, + CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, + CUJ_FOLD_ANIM, + CUJ_DESKTOP_MODE_RESIZE_WINDOW }) @Retention(RetentionPolicy.SOURCE) - public @interface CujType { - } + public @interface CujType {} private static final int NO_STATSD_LOGGING = -1; @@ -341,6 +365,9 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_LOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_LOCK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_UNLOCK; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW; } private Cuj() { @@ -543,6 +570,12 @@ public class Cuj { return "LAUNCHER_PRIVATE_SPACE_LOCK"; case CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK: return "LAUNCHER_PRIVATE_SPACE_UNLOCK"; + case CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW: + return "DESKTOP_MODE_MAXIMIZE_WINDOW"; + case CUJ_FOLD_ANIM: + return "FOLD_ANIM"; + case CUJ_DESKTOP_MODE_RESIZE_WINDOW: + return "DESKTOP_MODE_RESIZE_WINDOW"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/jank/OWNERS b/core/java/com/android/internal/jank/OWNERS index 2f3bbeef0d77..5463883a16eb 100644 --- a/core/java/com/android/internal/jank/OWNERS +++ b/core/java/com/android/internal/jank/OWNERS @@ -4,4 +4,5 @@ include /services/core/java/com/android/server/wm/OWNERS ahanwu@google.com vadimt@google.com marcinoc@google.com -pmuetschard@google.com
\ No newline at end of file +pmuetschard@google.com +nicomazz@google.com diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index 13efaf06aeda..754f77e72f8a 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -28,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK; @@ -227,7 +228,8 @@ public class LatencyTracker { public static final int ACTION_NOTIFICATION_BIG_PICTURE_LOADED = 23; /** - * Time it takes to unlock the device via udfps, until the whole launcher appears. + * Time it takes to unlock the device via fps, + * until either the launcher or the foreground app appears. */ public static final int ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME = 24; @@ -249,6 +251,12 @@ public class LatencyTracker { */ public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN = 27; + /** + * Time it takes to unlock the device via face, + * until either the launcher or the foreground app appears. + */ + public static final int ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME = 28; + private static final int[] ACTIONS_ALL = { ACTION_EXPAND_PANEL, ACTION_TOGGLE_RECENTS, @@ -278,6 +286,7 @@ public class LatencyTracker { ACTION_BACK_SYSTEM_ANIMATION, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, + ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME, }; /** @hide */ @@ -310,6 +319,7 @@ public class LatencyTracker { ACTION_BACK_SYSTEM_ANIMATION, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, + ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME, }) @Retention(RetentionPolicy.SOURCE) public @interface Action { @@ -345,6 +355,7 @@ public class LatencyTracker { UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION, UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME, }; private final Object mLock = new Object(); @@ -539,6 +550,8 @@ public class LatencyTracker { return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE"; case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN: return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN"; + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME: + return "ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME"; default: throw new IllegalArgumentException("Invalid action"); } diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index bd654fa0095c..2bfbf843b2ab 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -16,6 +16,8 @@ package com.android.internal.widget; +import static android.widget.flags.Flags.conversationLayoutUseMaximumChildHeight; + import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL; import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE; @@ -1407,6 +1409,38 @@ public class ConversationLayout extends FrameLayout } } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // ConversationLayout needs to set its height to its biggest child to show the content + // properly. + // FrameLayout measures its match_parent children twice when any of FLs dimension is not + // specified. However, its sets its own dimensions before the second measurement pass. + // Content CutOff happens when children have bigger height on its second measurement. + if (conversationLayoutUseMaximumChildHeight()) { + int maxHeight = getMeasuredHeight(); + final int count = getChildCount(); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child == null || child.getVisibility() == GONE) { + continue; + } + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + maxHeight = Math.max(maxHeight, + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + } + + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + if (maxHeight != getMeasuredHeight()) { + setMeasuredDimension(getMeasuredWidth(), maxHeight); + } + } + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); diff --git a/core/java/com/android/internal/widget/NotificationCloseButton.java b/core/java/com/android/internal/widget/NotificationCloseButton.java new file mode 100644 index 000000000000..bce266d71e43 --- /dev/null +++ b/core/java/com/android/internal/widget/NotificationCloseButton.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.widget; + +import android.annotation.ColorInt; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.view.RemotableViewMethod; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.RemoteViews; + +import com.android.internal.R; + +/** + * A close button in a notification + */ +@RemoteViews.RemoteView +public class NotificationCloseButton extends ImageView { + + @ColorInt private int mBackgroundColor; + @ColorInt private int mForegroundColor; + + public NotificationCloseButton(Context context) { + this(context, null, 0, 0); + } + + public NotificationCloseButton(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0, 0); + } + + public NotificationCloseButton(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public NotificationCloseButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + setContentDescription(mContext.getText(R.string.close_button_text)); + boolean notificationCloseButtonSupported = Resources.getSystem().getBoolean( + com.android.internal.R.bool.config_notificationCloseButtonSupported); + this.setVisibility(notificationCloseButtonSupported ? View.VISIBLE : View.GONE); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(Button.class.getName()); + } + + + private void updateColors() { + if (mBackgroundColor != 0) { + this.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor)); + } + if (mForegroundColor != 0) { + this.setImageTintList(ColorStateList.valueOf(mForegroundColor)); + } + } + + /** + * Set the color used for the foreground. + */ + @RemotableViewMethod + public void setForegroundColor(@ColorInt int color) { + mForegroundColor = color; + updateColors(); + } + + /** + * Sets the color used for the background. + */ + @RemotableViewMethod + public void setBackgroundColor(@ColorInt int color) { + mBackgroundColor = color; + updateColors(); + } +} diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 58bddaecd3e7..f5f04a7d50d1 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -22,7 +22,12 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -36,6 +41,13 @@ import android.widget.RemoteViews; @RemoteViews.RemoteView public class NotificationRowIconView extends CachingIconView { private boolean mApplyCircularCrop = false; + private boolean mShouldShowAppIcon = false; + + // Padding and background set on the view prior to being changed by setShouldShowAppIcon(true), + // to be restored if shouldShowAppIcon becomes false again. + private Rect mOriginalPadding = null; + private Drawable mOriginalBackground = null; + public NotificationRowIconView(Context context) { super(context); @@ -59,7 +71,7 @@ public class NotificationRowIconView extends CachingIconView { @Override protected void onFinishInflate() { // If showing the app icon, we don't need background or padding. - if (Flags.notificationsUseAppIcon() || Flags.notificationsUseAppIconInRow()) { + if (Flags.notificationsUseAppIcon()) { setPadding(0, 0, 0, 0); setBackground(null); } @@ -67,6 +79,42 @@ public class NotificationRowIconView extends CachingIconView { super.onFinishInflate(); } + /** Whether the icon represents the app icon (instead of the small icon). */ + @RemotableViewMethod + public void setShouldShowAppIcon(boolean shouldShowAppIcon) { + if (Flags.notificationsUseAppIconInRow()) { + if (mShouldShowAppIcon == shouldShowAppIcon) { + return; // no change + } + + mShouldShowAppIcon = shouldShowAppIcon; + if (mShouldShowAppIcon) { + if (mOriginalPadding == null && mOriginalBackground == null) { + mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(), + getPaddingRight(), getPaddingBottom()); + mOriginalBackground = getBackground(); + } + + setPadding(0, 0, 0, 0); + + // Make the background white in case the icon itself doesn't have one. + int white = Color.rgb(255, 255, 255); + ColorFilter colorFilter = new PorterDuffColorFilter(white, + PorterDuff.Mode.SRC_ATOP); + getBackground().mutate().setColorFilter(colorFilter); + } else { + // Restore original padding and background if needed + if (mOriginalPadding != null) { + setPadding(mOriginalPadding.left, mOriginalPadding.top, mOriginalPadding.right, + mOriginalPadding.bottom); + mOriginalPadding = null; + } + setBackground(mOriginalBackground); + mOriginalBackground = null; + } + } + } + @Nullable @Override Drawable loadSizeRestrictedIcon(@Nullable Icon icon) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index e21d1dfac865..911bb19b0ddc 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -111,6 +111,7 @@ cc_library_shared_for_libandroid_runtime { "libminikin", "libz", "server_configurable_flags", + "libaconfig_storage_read_api_cc", "android.database.sqlite-aconfig-cc", "android.media.audiopolicy-aconfig-cc", ], diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index 1d9b0db3218b..5a4d6dbdc085 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -146,6 +146,7 @@ message VibrationProto { IGNORED_FROM_VIRTUAL_DEVICE = 26; IGNORED_ON_WIRELESS_CHARGER = 27; IGNORED_MISSING_PERMISSION = 28; + CANCELLED_BY_APP_OPS = 29; reserved 17; // prev IGNORED_UNKNOWN_VIBRATION } } diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto new file mode 100644 index 000000000000..d24da0362e46 --- /dev/null +++ b/core/proto/android/widget/remoteviews.proto @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless optional by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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"; + +option java_multiple_files = true; + +package android.widget; + +import "frameworks/base/core/proto/android/privacy.proto"; + +/** + * An android.widget.RemoteViews object. This is used by RemoteViews.createPreviewFromProto + * and RemoteViews.writePreviewToProto. + * + * Any addition of fields here will require an update to the parsing code in RemoteViews.java. + * Otherwise the field will be ignored when parsing (with a logged warning). + * + * Do not change the tag number or type of any fields in order to maintain compatibility with + * previous versions. If a field is deleted, use `reserved` to mark its tag number. + */ +message RemoteViewsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 mode = 1; + optional string package_name = 2; + optional string layout_id = 3; + optional string light_background_layout_id = 4; + optional string view_id = 5; + optional SizeFProto ideal_size = 6; + optional int32 apply_flags = 7; + optional int64 provider_instance_id = 8; + // RemoteViews for different sizes (created with RemoteViews(Map<SizeF, RemoteViews) + // constructor). + repeated RemoteViewsProto sized_remoteviews = 9; + // RemoteViews for portrait/landscape (created with RemoteViews(RemoteViews, RemoteViews)i + // constructor). + optional RemoteViewsProto portrait_remoteviews = 10; + optional RemoteViewsProto landscape_remoteviews = 11; + optional bool is_root = 12; + optional bool has_draw_instructions = 13; +} + + +message SizeFProto { + optional float width = 1; + optional float height = 2; +} diff --git a/core/res/res/drawable/notification_close_button_icon.xml b/core/res/res/drawable/notification_close_button_icon.xml new file mode 100644 index 000000000000..947cd5a10525 --- /dev/null +++ b/core/res/res/drawable/notification_close_button_icon.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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="@dimen/notification_close_button_size" + android:height="@dimen/notification_close_button_size" + android:viewportWidth="16.0" + android:viewportHeight="16.0"> +<path + android:fillColor="#FF000000" + android:pathData="M 12.6667 4.2733 L 11.7267 3.3333 L 8 7.06 L 4.2734 3.3333 L 3.3334 +4.2733 L 7.06 8 L 3.3334 11.7267 L 4.2734 12.6667 L 8 8.94 L 11.7267 12.6667 L 12.6667 +11.7267 L 8.94 8 L 12.6667 4.2733 Z"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/layout/accessibility_button_chooser.xml b/core/res/res/layout/accessibility_button_chooser.xml index 2f97bae2a80e..f50af15875d8 100644 --- a/core/res/res/layout/accessibility_button_chooser.xml +++ b/core/res/res/layout/accessibility_button_chooser.xml @@ -47,6 +47,16 @@ android:paddingTop="8dp" android:paddingBottom="8dp"/> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/accessibility_button_prompt" + android:textAppearance="?attr/textAppearanceMedium" + android:text="@string/accessibility_button_instructional_text" + android:gravity="start|center_vertical" + android:paddingTop="8dp" + android:paddingBottom="8dp"/> + <GridView android:layout_width="match_parent" android:layout_height="wrap_content" @@ -57,16 +67,5 @@ android:horizontalSpacing="10dp" android:stretchMode="columnWidth" android:gravity="center"/> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/accessibility_button_prompt" - android:textAppearance="?attr/textAppearanceMedium" - android:text="@string/accessibility_button_instructional_text" - android:gravity="start|center_vertical" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:visibility="gone"/> </LinearLayout> </com.android.internal.widget.ResolverDrawerLayout> diff --git a/core/res/res/layout/notification_close_button.xml b/core/res/res/layout/notification_close_button.xml new file mode 100644 index 000000000000..5eff84e6981a --- /dev/null +++ b/core/res/res/layout/notification_close_button.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.internal.widget.NotificationCloseButton + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/close_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|end" + android:contentDescription="@string/close_button_text" + android:visibility="gone" + android:src="@drawable/notification_close_button_icon" + android:padding="2dp" + android:scaleType="fitCenter" + android:importantForAccessibility="no" + > +</com.android.internal.widget.NotificationCloseButton> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index d80b7657d0b0..e44c7272bc1e 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -83,11 +83,28 @@ android:focusable="false" /> - <include layout="@layout/notification_expand_button" + <LinearLayout + android:id="@+id/notification_buttons_column" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - /> + android:orientation="vertical" + > + + <include layout="@layout/notification_close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="end" + android:layout_marginEnd="20dp" + /> + + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + /> + + </LinearLayout> </NotificationHeaderView> diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index 452df50484c8..29f14a4a93fc 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -157,20 +157,38 @@ android:maxDrawableHeight="@dimen/notification_right_icon_size" /> - <FrameLayout - android:id="@+id/expand_button_touch_container" + <LinearLayout + android:id="@+id/notification_buttons_column" android:layout_width="wrap_content" android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" + android:layout_alignParentEnd="true" + android:orientation="vertical" > - <include layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical|end" + <include layout="@layout/notification_close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="end" + android:layout_marginEnd="20dp" /> - </FrameLayout> + <FrameLayout + android:id="@+id/expand_button_touch_container" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1" + android:minWidth="@dimen/notification_content_margin_end" + > + + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + /> + + </FrameLayout> + + </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index ce8a90495572..13f2c37d73fe 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -107,13 +107,20 @@ > <!--expand_button_container is dynamically placed between here and at the end of the layout. It starts here since only FrameLayout layout params have gravity--> - <FrameLayout + <LinearLayout android:id="@+id/expand_button_container" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="end|top" android:clipChildren="false" - android:clipToPadding="false"> + android:clipToPadding="false" + android:orientation="vertical"> + <include layout="@layout/notification_close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="end" + android:layout_marginEnd="20dp" + /> <!--expand_button_touch_container makes sure that we can nicely center the expand content in the collapsed layout while the parent makes sure that we're never laid out bigger than the messaging content.--> @@ -145,6 +152,6 @@ android:layout_gravity="center" /> </LinearLayout> - </FrameLayout> + </LinearLayout> </FrameLayout> </com.android.internal.widget.ConversationLayout> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 3c1ae773d867..b19bdf9636e1 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -1883,8 +1883,7 @@ <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN is too short. Must be at least 4 digits."</string> <string name="restr_pin_try_later" msgid="5897719962541636727">"Try again later"</string> <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string> - <!-- no translation found for immersive_cling_description (2896205051090870978) --> - <skip /> + <string name="immersive_cling_description" msgid="2896205051090870978">"To exit, swipe down from the top of your screen"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string> <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string> <string name="display_rotation_camera_compat_toast_in_multi_window" msgid="2473122980393502775">"Open <xliff:g id="NAME">%s</xliff:g> in full screen for a better view"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 557271457bb3..cb9bbf500092 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -1883,8 +1883,7 @@ <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN is too short. Must be at least 4 digits."</string> <string name="restr_pin_try_later" msgid="5897719962541636727">"Try again later"</string> <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string> - <!-- no translation found for immersive_cling_description (2896205051090870978) --> - <skip /> + <string name="immersive_cling_description" msgid="2896205051090870978">"To exit, swipe down from the top of your screen"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string> <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string> <string name="display_rotation_camera_compat_toast_in_multi_window" msgid="2473122980393502775">"Open <xliff:g id="NAME">%s</xliff:g> in full screen for a better view"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 4d350a05df76..8fe9a5bbdcaf 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1433,7 +1433,7 @@ <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ભાષા અને લેઆઉટ પસંદ કરવા માટે ટૅપ કરો"</string> <string name="fast_scroll_alphabet" msgid="8854435958703888376">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="2529539945421557329">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> - <string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"અન્ય ઍપથી ઉપર બતાવો"</string> + <string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"અન્ય ઍપની ઉપર ડિસ્પ્લે કરો"</string> <string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"<xliff:g id="NAME">%s</xliff:g> અન્ય ઍપ્લિકેશનોની ઉપર પ્રદર્શિત થઈ રહ્યું છે"</string> <string name="alert_windows_notification_title" msgid="6331662751095228536">"<xliff:g id="NAME">%s</xliff:g> અન્ય ઍપ્લિકેશનો પર દેખાઈ છે"</string> <string name="alert_windows_notification_message" msgid="6538171456970725333">"જો તમે નથી ઇચ્છતા કે <xliff:g id="NAME">%s</xliff:g> આ સુવિધાનો ઉપયોગ કરે, તો સેટિંગ ખોલવા માટે ટૅપ કરો અને તેને બંધ કરો."</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index f4ed8533be2f..d305c87f46d2 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -209,7 +209,7 @@ <string name="location_changed_notification_text" msgid="7158423339982706912">"Контактирајте со IT-администраторот за да дознаете повеќе"</string> <string name="geofencing_service" msgid="3826902410740315456">"Услуга за виртуелна географска граница"</string> <string name="country_detector" msgid="7023275114706088854">"Детектор на земја"</string> - <string name="location_service" msgid="2439187616018455546">"Услуга за локација"</string> + <string name="location_service" msgid="2439187616018455546">"Локациска услуга"</string> <string name="gnss_service" msgid="8907781262179951385">"Услуга GNSS"</string> <string name="sensor_notification_service" msgid="7474531979178682676">"Услуга за известување од сензорот"</string> <string name="twilight_service" msgid="8964898045693187224">"Услуга за самрак"</string> @@ -498,9 +498,9 @@ <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"пристапи кон наредби на давателот на дополнителна локација"</string> <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Овозможува апликацијата да пристапи кон дополнителни наредби на давател на локација. Ова може да овозможи апликацијата да го попечи функционирањето на GPS или други извори на локација."</string> <string name="permlab_accessFineLocation" msgid="6426318438195622966">"пристап до прецизната локација само во преден план"</string> - <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Апликацијава може да ја добие вашата прецизна локација од „Услугите според локација“ кога се користи. „Услугите според локација“ за уредот мора да се вклучени ако сакате апликацијата да ја добие локацијата. Ова може да го зголеми користењето на батеријата."</string> + <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Апликацијава може да ја добие вашата прецизна локација од „Локациските услуги“ кога се користи. „Локациските услуги“ за уредот мора да се вклучени ако сакате апликацијата да ја добие локацијата. Ова може да го зголеми користењето на батеријата."</string> <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"пристап до приближната локација само во преден план"</string> - <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Апликацијава може да ја добие вашата приближна локација од „Услугите според локација“ кога се користи. „Услугите според локација“ за уредот мора да се вклучени ако сакате апликацијата да ја добие локацијата."</string> + <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Апликацијава може да ја добие вашата приближна локација од „Локациските услуги“ кога се користи. „Локациските услуги“ за уредот мора да се вклучени ако сакате апликацијата да ја добие локацијата."</string> <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"пристап до локацијата во заднина"</string> <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Апликацијава може да пристапува до локацијата во секое време, дури и кога не се користи."</string> <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"менува аудио поставки"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index bc6c99c43987..6bc9fe0c467e 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1380,7 +1380,7 @@ <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Ativar serviço móvel"</string> <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Baixe o app da operadora para ativar seu novo chip"</string> <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Baixe o app <xliff:g id="APP_NAME">%1$s</xliff:g> para ativar seu novo chip"</string> - <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Fazer download do app"</string> + <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Baixar o app"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"Novo chip inserido"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"Toque para configurar"</string> <string name="time_picker_dialog_title" msgid="9053376764985220821">"Definir hora"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index bc6c99c43987..6bc9fe0c467e 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1380,7 +1380,7 @@ <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Ativar serviço móvel"</string> <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Baixe o app da operadora para ativar seu novo chip"</string> <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Baixe o app <xliff:g id="APP_NAME">%1$s</xliff:g> para ativar seu novo chip"</string> - <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Fazer download do app"</string> + <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Baixar o app"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"Novo chip inserido"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"Toque para configurar"</string> <string name="time_picker_dialog_title" msgid="9053376764985220821">"Definir hora"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index fe66eb965430..5c3f819829a9 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1433,7 +1433,7 @@ <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"輕觸即可選取語言和版面配置"</string> <string name="fast_scroll_alphabet" msgid="8854435958703888376">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="2529539945421557329">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> - <string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"顯示在其他應用程式上層"</string> + <string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"在其他應用程式上面顯示"</string> <string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"「<xliff:g id="NAME">%s</xliff:g>」在其他應用程式上顯示內容"</string> <string name="alert_windows_notification_title" msgid="6331662751095228536">"「<xliff:g id="NAME">%s</xliff:g>」正在其他應用程式上顯示內容"</string> <string name="alert_windows_notification_message" msgid="6538171456970725333">"如果你不想讓「<xliff:g id="NAME">%s</xliff:g>」使用這項功能,請輕觸開啟設定頁面,然後停用此功能。"</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 17666cfc60a0..335b74025da3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -567,14 +567,6 @@ It has been updated to affect other plug types. --> <bool name="config_keepDreamingWhenUnplugging">false</bool> - <!-- The timeout (in ms) to wait before attempting to reconnect to the dream overlay service if - it becomes disconnected --> - <integer name="config_dreamOverlayReconnectTimeoutMs">1000</integer> <!-- 1 second --> - <!-- The maximum number of times to attempt reconnecting to the dream overlay service --> - <integer name="config_dreamOverlayMaxReconnectAttempts">3</integer> - <!-- The duration after which the dream overlay connection should be considered stable --> - <integer name="config_minDreamOverlayDurationMs">10000</integer> <!-- 10 seconds --> - <!-- Auto-rotation behavior --> <!-- If true, enables auto-rotation features using the accelerometer. @@ -2633,10 +2625,7 @@ If false, not supported. --> <bool name="config_duplicate_port_omadm_wappush">false</bool> - <!-- Maximum numerical value that will be shown in a status bar - notification icon or in the notification itself. Will be replaced - with @string/status_bar_notification_info_overflow when shown in the - UI. --> + <!-- @deprecated No longer used. --> <integer name="status_bar_notification_info_maxnum">999</integer> <!-- Path to an ISO image to be shared with via USB mass storage. @@ -3917,6 +3906,7 @@ <item>countdown</item> <item>schedule</item> <item>event</item> + <item>custom_manual</item> </string-array> <!-- Priority repeat caller threshold, in minutes --> @@ -6972,6 +6962,9 @@ Note that, indefinitely repeating vibrations are not allowed as shutdown vibrations. --> <string name="config_defaultShutdownVibrationFile" /> + <!-- Whether or not vibration is disabled during shutdown --> + <bool name="config_disableShutdownVibrationInZen">false</bool> + <!-- Whether single finger panning is enabled by default when magnification is on --> <bool name="config_enable_a11y_magnification_single_panning">false</bool> @@ -7088,6 +7081,9 @@ <!-- Whether the system uses auto-suspend mode. --> <bool name="config_useAutoSuspend">true</bool> + <!-- Whether close/dismiss buttons are supported on notifications. --> + <bool name="config_notificationCloseButtonSupported">false</bool> + <!-- Whether to show GAIA education screen during account login of private space setup. OEM/Partner can explicitly opt to disable the screen. --> <bool name="config_enableGaiaEducationInPrivateSpace">true</bool> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 5fea51599971..6cba84be58c3 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -355,6 +355,9 @@ <!-- the padding of the expand icon in the notification header --> <dimen name="notification_expand_button_icon_padding">2dp</dimen> + <!-- the size of the notification close button --> + <dimen name="notification_close_button_size">16dp</dimen> + <!-- Vertical margin for the headerless notification content, when content has 1 line --> <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) --> <dimen name="notification_headerless_margin_oneline">16dp</dimen> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 10373a59d819..9d1e86b7ecb9 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -788,11 +788,7 @@ <!-- label for item that locks the phone and enforces that it can't be unlocked without strong authentication. [CHAR LIMIT=24] --> <string name="global_action_lockdown">Lockdown</string> - <!-- Text to use when the number in a notification info is too large - (greater than status_bar_notification_info_maxnum, defined in - values/config.xml) and must be truncated. May need to be localized - for most appropriate textual indicator of "more than X". - [CHAR LIMIT=4] --> + <!-- @deprecated No longer used. --> <string name="status_bar_notification_info_overflow">999+</string> <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] --> @@ -3837,6 +3833,11 @@ <!-- Message of notification shown when Test Harness Mode is enabled. [CHAR LIMIT=NONE] --> <string name="test_harness_mode_notification_message">Perform a factory reset to disable Test Harness Mode.</string> + <!-- Title of notification shown when device is in the wrong Headless System User Mode configuration. [CHAR LIMIT=NONE] --> + <string name="wrong_hsum_configuration_notification_title">Wrong HSUM build configuration</string> + <!-- Message of notification shown when device is in the wrong Headless System User Mode configuration. [CHAR LIMIT=NONE] --> + <string name="wrong_hsum_configuration_notification_message">The Headless System User Mode state of this device differs from its build configuration. Please factory reset the device.</string> + <!-- Title of notification shown when serial console is enabled. [CHAR LIMIT=NONE] --> <string name="console_running_notification_title">Serial console enabled</string> <!-- Message of notification shown when serial console is enabled. [CHAR LIMIT=NONE] --> @@ -4844,19 +4845,19 @@ <!-- Text spoken when accessibility shortcut warning dialog is shown. [CHAR LIMIT=none] --> <string name="accessibility_shortcut_spoken_feedback">Release the volume keys. To turn on <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>, press and hold both volume keys again for 3 seconds.</string> - <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. [CHAR LIMIT=none]--> - <string name="accessibility_button_prompt_text">Choose a feature to use when you tap the accessibility button:</string> + <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar or in gesture navigation. [CHAR LIMIT=none]--> + <string name="accessibility_button_prompt_text">Choose a feature</string> <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button when gesture navigation is enabled [CHAR LIMIT=none] --> - <string name="accessibility_gesture_prompt_text">Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with two fingers):</string> + <string name="accessibility_gesture_prompt_text">Choose a feature</string> <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button when gesture navigation and TalkBack is enabled [CHAR LIMIT=none] --> - <string name="accessibility_gesture_3finger_prompt_text">Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with three fingers):</string> + <string name="accessibility_gesture_3finger_prompt_text">Choose a feature</string> <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. [CHAR LIMIT=none]--> - <string name="accessibility_button_instructional_text">To switch between features, touch & hold the accessibility button.</string> + <string name="accessibility_button_instructional_text">The feature will open next time you tap the accessibility button.</string> <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button when gesture navigation is enabled. [CHAR LIMIT=none] --> - <string name="accessibility_gesture_instructional_text">To switch between features, swipe up with two fingers and hold.</string> + <string name="accessibility_gesture_instructional_text">The feature will open next time you use this shortcut. Swipe up with two fingers from the bottom of your screen and release quickly.</string> <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button when gesture navigation and TalkBack is enabled. [CHAR LIMIT=none] --> - <string name="accessibility_gesture_3finger_instructional_text">To switch between features, swipe up with three fingers and hold.</string> + <string name="accessibility_gesture_3finger_instructional_text">The feature will open next time you use this shortcut. Swipe up with three fingers from the bottom of your screen and release quickly.</string> <!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. --> <string name="accessibility_magnification_chooser_text">Magnification</string> @@ -5430,6 +5431,10 @@ <string name="call_notification_screening_text">Screening an incoming call</string> <string name="default_notification_channel_label">Uncategorized</string> + <string name="promotional_notification_channel_label">Promotions</string> + <string name="social_notification_channel_label">Social</string> + <string name="news_notification_channel_label">News</string> + <string name="recs_notification_channel_label">Recommendations</string> <string name="importance_from_user">You set the importance of these notifications.</string> <string name="importance_from_person">This is important because of the people involved.</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 06a4d550b204..8823894e18c8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -521,6 +521,7 @@ <java-symbol type="bool" name="config_preferKeepClearForFocus" /> <java-symbol type="bool" name="config_hibernationDeletesOatArtifactsEnabled"/> <java-symbol type="integer" name="config_defaultAnalogClockSecondsHandFps"/> + <java-symbol type="bool" name="config_notificationCloseButtonSupported"/> <java-symbol type="bool" name="config_enableGaiaEducationInPrivateSpace"/> <java-symbol type="color" name="tab_indicator_text_v4" /> @@ -2152,6 +2153,8 @@ <java-symbol type="string" name="adbwifi_active_notification_title" /> <java-symbol type="string" name="test_harness_mode_notification_title" /> <java-symbol type="string" name="test_harness_mode_notification_message" /> + <java-symbol type="string" name="wrong_hsum_configuration_notification_title" /> + <java-symbol type="string" name="wrong_hsum_configuration_notification_message" /> <java-symbol type="string" name="console_running_notification_title" /> <java-symbol type="string" name="console_running_notification_message" /> <java-symbol type="string" name="mte_override_notification_title" /> @@ -2298,9 +2301,6 @@ <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> <java-symbol type="bool" name="config_resetScreenTimeoutOnUnexpectedDreamExit" /> - <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" /> - <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" /> - <java-symbol type="integer" name="config_minDreamOverlayDurationMs" /> <java-symbol type="array" name="config_loggable_dream_prefixes" /> <java-symbol type="string" name="config_dozeComponent" /> <java-symbol type="string" name="enable_explore_by_touch_warning_title" /> @@ -3171,6 +3171,7 @@ <java-symbol type="id" name="header_text_secondary_divider" /> <java-symbol type="drawable" name="ic_expand_notification" /> <java-symbol type="drawable" name="ic_collapse_notification" /> + <java-symbol type="drawable" name="notification_close_button_icon" /> <java-symbol type="drawable" name="ic_expand_bundle" /> <java-symbol type="drawable" name="ic_collapse_bundle" /> <java-symbol type="drawable" name="ic_notification_summary_auto" /> @@ -3877,6 +3878,7 @@ <java-symbol type="string" name="expand_button_content_description_collapsed" /> <java-symbol type="string" name="expand_button_content_description_expanded" /> + <java-symbol type="string" name="close_button_text" /> <java-symbol type="string" name="content_description_collapsed" /> <java-symbol type="string" name="content_description_expanded" /> @@ -5044,6 +5046,9 @@ <java-symbol type="string" name="ui_translation_accessibility_translation_finished" /> <java-symbol type="layout" name="notification_expand_button"/> + <java-symbol type="id" name="close_button" /> + <java-symbol type="layout" name="notification_close_button"/> + <java-symbol type="id" name="notification_buttons_column" /> <java-symbol type="bool" name="config_supportsMicToggle" /> <java-symbol type="bool" name="config_supportsCamToggle" /> @@ -5427,6 +5432,7 @@ <java-symbol type="drawable" name="focus_event_pressed_key_background" /> <java-symbol type="drawable" name="focus_event_rotary_input_background" /> <java-symbol type="string" name="config_defaultShutdownVibrationFile" /> + <java-symbol type="bool" name="config_disableShutdownVibrationInZen" /> <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" /> <java-symbol type="bool" name="config_enable_a11y_magnification_single_panning" /> @@ -5518,6 +5524,12 @@ <java-symbol type="string" name="biometric_dangling_notification_action_set_up" /> <java-symbol type="string" name="biometric_dangling_notification_action_not_now" /> + <!-- Notification bundles --> + <java-symbol type="string" name="promotional_notification_channel_label"/> + <java-symbol type="string" name="social_notification_channel_label"/> + <java-symbol type="string" name="news_notification_channel_label"/> + <java-symbol type="string" name="recs_notification_channel_label"/> + <!-- Priority Modes icons --> <java-symbol type="drawable" name="ic_zen_mode_type_bedtime" /> <java-symbol type="drawable" name="ic_zen_mode_type_driving" /> @@ -5528,5 +5540,4 @@ <java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" /> <java-symbol type="drawable" name="ic_zen_mode_type_theater" /> <java-symbol type="drawable" name="ic_zen_mode_type_unknown" /> - </resources> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java index b3a0aba0d352..db3a2da56b08 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java @@ -470,6 +470,20 @@ public final class RadioMetadataTest extends ExtendedRadioMockitoTestCase { } @Test + public void equals_forMetadataWithDifferentContents_returnsFalse() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + RadioMetadata metadata1 = mBuilder + .putStringArray(RadioMetadata.METADATA_KEY_UFIDS, UFIDS_VALUE) + .build(); + RadioMetadata metadata2 = mBuilder + .putStringArray(RadioMetadata.METADATA_KEY_UFIDS, new String[]{"ufid3", "ufid2"}) + .build(); + + mExpect.withMessage("Metadata with the same contents") + .that(metadata1).isNotEqualTo(metadata2); + } + + @Test public void describeContents_forMetadata() { RadioMetadata metadata = mBuilder.build(); @@ -553,4 +567,25 @@ public final class RadioMetadataTest extends ExtendedRadioMockitoTestCase { .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON)) .isEqualTo(bitmapResized); } + + @Test + public void toString_containsMetadataValues() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + RadioMetadata metadataExpected = mBuilder + .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) + .putStringArray(RadioMetadata.METADATA_KEY_UFIDS, UFIDS_VALUE) + .build(); + + String metadateString = metadataExpected.toString(); + + mExpect.withMessage("RDS PI value in converted sting for metadata") + .that(metadateString).contains(Integer.toString(INT_KEY_VALUE)); + mExpect.withMessage("Artist value in converted sting for metadata") + .that(metadateString).contains(ARTIST_KEY_VALUE); + for (int i = 0; i < UFIDS_VALUE.length; i++) { + mExpect.withMessage("UFIDs[%s] value in converted sting for metadata", i) + .that(metadateString).contains(UFIDS_VALUE[i]); + } + } } diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java index 86a6744d7121..5a08acdf6271 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java @@ -255,6 +255,17 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test + public void throwOnError_withUnknownErrorCode() { + int invalidErrorCode = 100; + ServiceSpecificException halException = new ServiceSpecificException(invalidErrorCode); + + RuntimeException thrown = ConversionUtils.throwOnError(halException, "seek"); + + expect.withMessage("Exception thrown for unknown error code") + .that(thrown).hasMessageThat().contains("seek: unknown error"); + } + + @Test public void propertiesFromHalProperties_idsMatch() { expect.withMessage("Properties id") .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID); @@ -296,7 +307,7 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { Map<String, Integer> dabTableExpected = Map.of(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1, DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2); - expect.withMessage("Supported program types") + expect.withMessage("DAB frequency table") .that(MODULE_PROPERTIES.getDabFrequencyTable()) .containsExactlyEntriesIn(dabTableExpected); } @@ -621,7 +632,7 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { @Test public void programInfoMeetsSdkVersionRequirement_withLowerVersionIdForRelatedContent() { RadioManager.ProgramInfo dabProgramInfo = new RadioManager.ProgramInfo( - TEST_DAB_SELECTOR_LEGACY, TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, + TEST_DAB_SELECTOR_LEGACY, TEST_DAB_SID_EXT_LEGACY_ID, TEST_DAB_FREQUENCY_ID, List.of(TEST_DAB_SID_EXT_ID), /* infoFlags= */ 0, TEST_SIGNAL_QUALITY, new RadioMetadata.Builder().build(), new ArrayMap<>()); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java index 98641ef8bcc0..c5c3a614a82f 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java @@ -32,6 +32,7 @@ import android.hardware.radio.Announcement; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; +import android.os.ParcelableException; import android.util.ArrayMap; import com.google.common.truth.Expect; @@ -89,6 +90,15 @@ public final class ConvertTest { } @Test + public void throwOnError_withUnknownErrorCode() { + ParcelableException thrown = assertThrows(ParcelableException.class, () -> + Convert.throwOnError("tune", /* result= */ 1000)); + + expect.withMessage("Exception for unknown error code").that(thrown) + .hasMessageThat().contains("unknown error"); + } + + @Test public void propertiesFromHalProperties_idsMatch() { expect.withMessage("Properties id") .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID); @@ -232,6 +242,12 @@ public final class ConvertTest { } @Test + public void vendorInfoFromHalVendorKeyValues_withNull() { + expect.withMessage("Null vendor info converted from HAL") + .that(Convert.vendorInfoFromHal(/* info= */ null)).isEmpty(); + } + + @Test public void vendorInfoFromHalVendorKeyValues_withNullElements() { VendorKeyValue halVendorInfo = new VendorKeyValue(); halVendorInfo.key = null; diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java index 12a42f975cd7..ed5e4aff0279 100644 --- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java +++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java @@ -37,15 +37,20 @@ public class ChangeReporterTest { long myChangeId = 500L, otherChangeId = 600L; int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED; - assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState)); + assertTrue(reporter.shouldWriteToStatsLog(false, + reporter.isAlreadyReported(myUid, myChangeId, myState))); reporter.reportChange(myUid, myChangeId, myState); // Same report will not be logged again. - assertFalse(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState)); + assertFalse(reporter.shouldWriteToStatsLog(false, + reporter.isAlreadyReported(myUid, myChangeId, myState))); // Other reports will be logged. - assertTrue(reporter.shouldWriteToStatsLog(otherUid, myChangeId, myState)); - assertTrue(reporter.shouldWriteToStatsLog(myUid, otherChangeId, myState)); - assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, otherState)); + assertTrue(reporter.shouldWriteToStatsLog(false, + reporter.isAlreadyReported(otherUid, myChangeId, myState))); + assertTrue(reporter.shouldWriteToStatsLog(false, + reporter.isAlreadyReported(myUid, otherChangeId, myState))); + assertTrue(reporter.shouldWriteToStatsLog(false, + reporter.isAlreadyReported(myUid, myChangeId, otherState))); } @Test @@ -55,15 +60,18 @@ public class ChangeReporterTest { long myChangeId = 500L; int myState = ChangeReporter.STATE_ENABLED; - assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState)); + assertTrue(reporter.shouldWriteToStatsLog(false, + reporter.isAlreadyReported(myUid, myChangeId, myState))); reporter.reportChange(myUid, myChangeId, myState); // Same report will not be logged again. - assertFalse(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState)); + assertFalse(reporter.shouldWriteToStatsLog(false, + reporter.isAlreadyReported(myUid, myChangeId, myState))); reporter.resetReportedChanges(myUid); // Same report will be logged again after reset. - assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState)); + assertTrue(reporter.shouldWriteToStatsLog(false, + reporter.isAlreadyReported(myUid, myChangeId, myState))); } @Test @@ -196,4 +204,21 @@ public class ChangeReporterTest { // off. assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false)); } + + @Test + public void testDontLogSystemApps() { + // Verify we don't log an app if we know it's a system app when source is system server. + ChangeReporter systemServerReporter = + new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); + + assertFalse(systemServerReporter.shouldWriteToStatsLog(true, false)); + assertFalse(systemServerReporter.shouldWriteToStatsLog(true, true)); + + // Verify we don't log an app if we know it's a system app when source is unknown. + ChangeReporter unknownReporter = + new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); + + assertFalse(unknownReporter.shouldWriteToStatsLog(true, false)); + assertFalse(unknownReporter.shouldWriteToStatsLog(true, true)); + } } diff --git a/core/tests/bugreports/Android.bp b/core/tests/bugreports/Android.bp index 7c1ac487bfdb..15e07e535b39 100644 --- a/core/tests/bugreports/Android.bp +++ b/core/tests/bugreports/Android.bp @@ -30,6 +30,7 @@ android_test { "android.test.base", ], static_libs: [ + "android.tracing.flags-aconfig-java", "androidx.test.rules", "androidx.test.uiautomator_uiautomator", "truth", diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java index 8072d6925675..7294d4c12577 100644 --- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java +++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java @@ -71,6 +71,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -102,17 +103,11 @@ public class BugreportManagerTest { // associated with the bugreport). private static final String INTENT_BUGREPORT_FINISHED = "com.android.internal.intent.action.BUGREPORT_FINISHED"; - private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT"; - private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT"; - private static final Path[] UI_TRACES_PREDUMPED = { + private ArrayList<Path> mUiTracesPreDumped = new ArrayList<>(Arrays.asList( Paths.get("/data/misc/perfetto-traces/bugreport/systrace.pftrace"), - Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"), - Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"), - Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"), - Paths.get("/data/misc/wmtrace/wm_trace.winscope"), - Paths.get("/data/misc/wmtrace/wm_log.winscope"), - }; + Paths.get("/data/misc/wmtrace/wm_trace.winscope") + )); private Handler mHandler; private Executor mExecutor; @@ -124,6 +119,17 @@ public class BugreportManagerTest { @Before public void setup() throws Exception { + if (!android.tracing.Flags.perfettoIme()) { + mUiTracesPreDumped.add(Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope")); + mUiTracesPreDumped.add( + Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope")); + mUiTracesPreDumped.add(Paths.get("/data/misc/wmtrace/ime_trace_service.winscope")); + } + + if (!android.tracing.Flags.perfettoProtologTracing()) { + mUiTracesPreDumped.add(Paths.get("/data/misc/wmtrace/wm_log.winscope")); + } + mHandler = createHandler(); mExecutor = (runnable) -> { if (mHandler != null) { @@ -206,7 +212,7 @@ public class BugreportManagerTest { mBrm.preDumpUiData(); waitTillDumpstateExitedOrTimeout(); - List<File> expectedPreDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED); + List<File> expectedPreDumpedTraceFiles = copyFiles(mUiTracesPreDumped); BugreportCallbackImpl callback = new BugreportCallbackImpl(); mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor, @@ -220,9 +226,9 @@ public class BugreportManagerTest { assertThat(mBugreportFile.length()).isGreaterThan(0L); assertFdsAreClosed(mBugreportFd); - assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED); + assertThatBugreportContainsFiles(mUiTracesPreDumped); - List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED); + List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(mUiTracesPreDumped); assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles); } @@ -235,9 +241,9 @@ public class BugreportManagerTest { // In some corner cases, data dumped as part of the full bugreport could be the same as the // pre-dumped data and this test would fail. Hence, here we create fake/artificial // pre-dumped data that we know it won't match with the full bugreport data. - createFakeTraceFiles(UI_TRACES_PREDUMPED); + createFakeTraceFiles(mUiTracesPreDumped); - List<File> preDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED); + List<File> preDumpedTraceFiles = copyFiles(mUiTracesPreDumped); BugreportCallbackImpl callback = new BugreportCallbackImpl(); mBrm.startBugreport(mBugreportFd, null, full(), mExecutor, @@ -251,9 +257,9 @@ public class BugreportManagerTest { assertThat(mBugreportFile.length()).isGreaterThan(0L); assertFdsAreClosed(mBugreportFd); - assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED); + assertThatBugreportContainsFiles(mUiTracesPreDumped); - List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED); + List<File> actualTraceFiles = extractFilesFromBugreport(mUiTracesPreDumped); assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles); } @@ -270,7 +276,7 @@ public class BugreportManagerTest { // 1. Pre-dump data // 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK) // 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK) - removeFilesIfNeeded(UI_TRACES_PREDUMPED); + removeFilesIfNeeded(mUiTracesPreDumped); // Start bugreport with "use predump" flag. Because the pre-dumped data is not available // the flag will be ignored and data will be dumped as in normal flow. @@ -286,7 +292,7 @@ public class BugreportManagerTest { assertThat(mBugreportFile.length()).isGreaterThan(0L); assertFdsAreClosed(mBugreportFd); - assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED); + assertThatBugreportContainsFiles(mUiTracesPreDumped); } @Test @@ -555,7 +561,7 @@ public class BugreportManagerTest { ); } - private void assertThatBugreportContainsFiles(Path[] paths) + private void assertThatBugreportContainsFiles(List<Path> paths) throws IOException { List<Path> entries = listZipArchiveEntries(mBugreportFile); for (Path pathInDevice : paths) { @@ -564,7 +570,7 @@ public class BugreportManagerTest { } } - private List<File> extractFilesFromBugreport(Path[] paths) throws Exception { + private List<File> extractFilesFromBugreport(List<Path> paths) throws Exception { List<File> files = new ArrayList<File>(); for (Path pathInDevice : paths) { Path pathInArchive = Paths.get("FS" + pathInDevice.toString()); @@ -614,7 +620,7 @@ public class BugreportManagerTest { return extractedFile; } - private static void createFakeTraceFiles(Path[] paths) throws Exception { + private static void createFakeTraceFiles(List<Path> paths) throws Exception { File src = createTempFile("fake", ".data"); Files.write("fake data".getBytes(StandardCharsets.UTF_8), src); @@ -631,7 +637,7 @@ public class BugreportManagerTest { ); } - private static List<File> copyFiles(Path[] paths) throws Exception { + private static List<File> copyFiles(List<Path> paths) throws Exception { ArrayList<File> files = new ArrayList<File>(); for (Path src : paths) { File dst = createTempFile(src.getFileName().toString(), ".copy"); @@ -643,7 +649,7 @@ public class BugreportManagerTest { return files; } - private static void removeFilesIfNeeded(Path[] paths) throws Exception { + private static void removeFilesIfNeeded(List<Path> paths) throws Exception { for (Path path : paths) { InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( "rm -f " + path.toString() diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 41696dfa782e..d2771691c50c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -63,6 +63,7 @@ android_test { "-c fa", ], static_libs: [ + "A11yChecker", "collector-device-lib-platform", "frameworks-base-testutils", "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index b64eeca04101..e9ad1c28578a 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -804,6 +804,7 @@ public class NotificationTest { } @Test + @Ignore // b/347089000 - Restore or delete public void testColors_ensureColors_colorized_producesValidPalette_white() { validateColorizedPaletteForColor(Color.WHITE); } diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index ee1d1e1b975c..3104f16bf5a1 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -21,7 +21,6 @@ import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG; -import static com.android.window.flags.Flags.FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -192,8 +191,6 @@ public class ClientTransactionListenerControllerTest { @Test public void testWindowTokenClient_onConfigurationChanged() { - mSetFlagsRule.enableFlags(FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE); - doNothing().when(mController).onContextConfigurationPreChanged(any()); doNothing().when(mController).onContextConfigurationPostChanged(any()); diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java index ca9154280a10..5464ea3c8310 100644 --- a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java +++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -209,6 +210,25 @@ public class BiometricPromptTest { "The number of list items exceeds "); } + @Test + public void testOnDialogDismissed_dialogDismissedNegative() throws RemoteException { + final ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor = + ArgumentCaptor.forClass(IBiometricServiceReceiver.class); + final BiometricPrompt.AuthenticationCallback callback = + mock(BiometricPrompt.AuthenticationCallback.class); + mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback); + mLooper.dispatchAll(); + + verify(mService).authenticate(any(), anyLong(), anyInt(), + biometricServiceReceiverCaptor.capture(), anyString(), any()); + + biometricServiceReceiverCaptor.getValue().onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_NEGATIVE); + + verify(callback).onAuthenticationError(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, + null /* errString */); + } + private String generateRandomString(int charNum) { final Random random = new Random(); final StringBuilder longString = new StringBuilder(charNum); diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java index 851e61259241..8cd6773936ef 100644 --- a/core/tests/coretests/src/android/os/MessageQueueTest.java +++ b/core/tests/coretests/src/android/os/MessageQueueTest.java @@ -16,7 +16,6 @@ package android.os; -import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.MediumTest; @@ -154,7 +153,6 @@ public class MessageQueueTest { @Test @MediumTest - @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testFieldIntegrity() throws Exception { TestHandlerThread tester = new TestFieldIntegrityHandler() { diff --git a/core/tests/coretests/src/android/util/SequenceUtilsTest.java b/core/tests/coretests/src/android/util/SequenceUtilsTest.java new file mode 100644 index 000000000000..020520dbcf85 --- /dev/null +++ b/core/tests/coretests/src/android/util/SequenceUtilsTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + + +import static android.util.SequenceUtils.getInitSeq; +import static android.util.SequenceUtils.getNextSeq; +import static android.util.SequenceUtils.isIncomingSeqNewer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.DisabledOnRavenwood; +import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for subtypes of {@link SequenceUtils}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:SequenceUtilsTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +@DisabledOnRavenwood(blockedBy = SequenceUtils.class) +public class SequenceUtilsTest { + + // This is needed to disable the test in Ravenwood test, because SequenceUtils hasn't opted in + // for Ravenwood, which is still in experiment. + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test + public void testNextSeq() { + assertEquals(getInitSeq() + 1, getNextSeq(getInitSeq())); + assertEquals(getInitSeq() + 1, getNextSeq(Integer.MAX_VALUE)); + } + + @Test + public void testIsIncomingSeqNewer() { + assertTrue(isIncomingSeqNewer(getInitSeq() + 1, getInitSeq() + 10)); + assertFalse(isIncomingSeqNewer(getInitSeq() + 10, getInitSeq() + 1)); + assertTrue(isIncomingSeqNewer(-100, 100)); + assertFalse(isIncomingSeqNewer(100, -100)); + assertTrue(isIncomingSeqNewer(1, 2)); + assertFalse(isIncomingSeqNewer(2, 1)); + + // Possible incoming seq are all newer than the initial seq. + assertTrue(isIncomingSeqNewer(getInitSeq(), getInitSeq() + 1)); + assertTrue(isIncomingSeqNewer(getInitSeq(), -100)); + assertTrue(isIncomingSeqNewer(getInitSeq(), 0)); + assertTrue(isIncomingSeqNewer(getInitSeq(), 100)); + assertTrue(isIncomingSeqNewer(getInitSeq(), Integer.MAX_VALUE)); + assertTrue(isIncomingSeqNewer(getInitSeq(), getNextSeq(Integer.MAX_VALUE))); + + // False for the same seq. + assertFalse(isIncomingSeqNewer(getInitSeq(), getInitSeq())); + assertFalse(isIncomingSeqNewer(100, 100)); + assertFalse(isIncomingSeqNewer(Integer.MAX_VALUE, Integer.MAX_VALUE)); + + // True when there is a large jump (overflow). + assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getInitSeq() + 1)); + assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getInitSeq() + 100)); + assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getNextSeq(Integer.MAX_VALUE))); + } +} diff --git a/core/tests/coretests/src/android/util/SparseSetArrayTest.java b/core/tests/coretests/src/android/util/SparseSetArrayTest.java index 1c72185ea93c..a8dce7032fa3 100644 --- a/core/tests/coretests/src/android/util/SparseSetArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseSetArrayTest.java @@ -17,7 +17,6 @@ package android.util; import static com.google.common.truth.Truth.assertThat; -import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; @@ -37,7 +36,6 @@ public class SparseSetArrayTest { public final RavenwoodRule mRavenwood = new RavenwoodRule(); @Test - @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testAddAll() { final SparseSetArray<Integer> sparseSetArray = new SparseSetArray<>(); @@ -59,7 +57,6 @@ public class SparseSetArrayTest { } @Test - @IgnoreUnderRavenwood(reason = "b/315036461") public void testCopyConstructor() { final SparseSetArray<Integer> sparseSetArray = new SparseSetArray<>(); diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 06cb0eea811b..b15370049176 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -1250,6 +1250,81 @@ public class ViewRootImplTest { }); } + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY}) + public void votePreferredFrameRate_infrequentLayer_smallView_voteForLow() throws Throwable { + if (!ViewProperties.vrr_enabled().orElse(true)) { + return; + } + final long delay = 200L; + + mView = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + wmlp.width = 1; + wmlp.height = 1; + + // The view is a small view, and it should vote for category low only. + int expected = FRAME_RATE_CATEGORY_LOW; + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + wm.addView(mView, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + mViewRootImpl = mView.getViewRootImpl(); + waitForFrameRateCategoryToSettle(mView); + + // In transition from frequent update to infrequent update + Thread.sleep(delay); + sInstrumentation.runOnMainSync(() -> { + mView.invalidate(); + runAfterDraw(() -> assertEquals(expected, + mViewRootImpl.getLastPreferredFrameRateCategory())); + }); + waitForAfterDraw(); + + // In transition from frequent update to infrequent update + Thread.sleep(delay); + sInstrumentation.runOnMainSync(() -> { + mView.invalidate(); + runAfterDraw(() -> assertEquals(expected, + mViewRootImpl.getLastPreferredFrameRateCategory())); + }); + + // Infrequent update + Thread.sleep(delay); + + // The view is small, the expected category is still low for intermittent. + int intermittentExpected = FRAME_RATE_CATEGORY_LOW; + + sInstrumentation.runOnMainSync(() -> { + mView.invalidate(); + runAfterDraw(() -> assertEquals(intermittentExpected, + mViewRootImpl.getLastPreferredFrameRateCategory())); + }); + waitForAfterDraw(); + + // When the View vote, it's still considered as intermittent update state + sInstrumentation.runOnMainSync(() -> { + mView.invalidate(); + runAfterDraw(() -> assertEquals(intermittentExpected, + mViewRootImpl.getLastPreferredFrameRateCategory())); + }); + waitForAfterDraw(); + + // Becomes frequent update state + sInstrumentation.runOnMainSync(() -> { + mView.invalidate(); + runAfterDraw(() -> assertEquals(expected, + mViewRootImpl.getLastPreferredFrameRateCategory())); + }); + } + /** * Test the IsFrameRatePowerSavingsBalanced values are properly set */ diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java b/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java new file mode 100644 index 000000000000..438277b272cb --- /dev/null +++ b/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.accessibility.a11ychecker; + +import static android.view.accessibility.a11ychecker.MockAccessibilityNodeInfoBuilder.PACKAGE_NAME; + +import static com.google.common.truth.Truth.assertThat; + +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.widget.RecyclerView; + +import com.google.common.collect.ImmutableList; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class AccessibilityNodePathBuilderTest { + + public static final String RESOURCE_ID_PREFIX = PACKAGE_NAME + ":id/"; + + @Test + public void createNodePath_pathWithResourceNames() { + AccessibilityNodeInfo child = new MockAccessibilityNodeInfoBuilder() + .setViewIdResourceName(RESOURCE_ID_PREFIX + "child_node") + .build(); + AccessibilityNodeInfo parent = + new MockAccessibilityNodeInfoBuilder() + .setViewIdResourceName(RESOURCE_ID_PREFIX + "parent_node") + .addChildren(ImmutableList.of(child)) + .build(); + AccessibilityNodeInfo root = + new MockAccessibilityNodeInfoBuilder() + .setViewIdResourceName(RESOURCE_ID_PREFIX + "root_node") + .addChildren(ImmutableList.of(parent)) + .build(); + + assertThat(AccessibilityNodePathBuilder.createNodePath(child)) + .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(parent)) + .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(root)) + .isEqualTo(PACKAGE_NAME + ":root_node"); + } + + @Test + public void createNodePath_pathWithoutResourceNames() { + AccessibilityNodeInfo child = + new MockAccessibilityNodeInfoBuilder() + .setClassName(TextView.class.getName()) + .build(); + AccessibilityNodeInfo parent = + + new MockAccessibilityNodeInfoBuilder() + .setClassName(RecyclerView.class.getName()) + .addChildren(ImmutableList.of(child)) + .build(); + AccessibilityNodeInfo root = + new MockAccessibilityNodeInfoBuilder() + .setClassName(FrameLayout.class.getName()) + .addChildren(ImmutableList.of(parent)) + .build(); + + assertThat(AccessibilityNodePathBuilder.createNodePath(child)) + .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(parent)) + .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(root)) + .isEqualTo(PACKAGE_NAME + ":FrameLayout"); + } + + @Test + public void createNodePath_parentWithMultipleChildren() { + AccessibilityNodeInfo child1 = + new MockAccessibilityNodeInfoBuilder() + .setViewIdResourceName(RESOURCE_ID_PREFIX + "child1") + .build(); + AccessibilityNodeInfo child2 = + new MockAccessibilityNodeInfoBuilder() + .setClassName(TextView.class.getName()) + .build(); + AccessibilityNodeInfo parent = + new MockAccessibilityNodeInfoBuilder() + .setClassName(FrameLayout.class.getName()) + .addChildren(ImmutableList.of(child1, child2)) + .build(); + + assertThat(AccessibilityNodePathBuilder.createNodePath(child1)) + .isEqualTo(PACKAGE_NAME + ":FrameLayout/child1[1]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(child2)) + .isEqualTo(PACKAGE_NAME + ":FrameLayout/TextView[2]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(parent)) + .isEqualTo(PACKAGE_NAME + ":FrameLayout"); + } + + @Test + public void createNodePath_handlesDifferentIdFormats() { + AccessibilityNodeInfo child1 = + new MockAccessibilityNodeInfoBuilder() + .setViewIdResourceName(RESOURCE_ID_PREFIX + "childId") + .build(); + AccessibilityNodeInfo child2 = + new MockAccessibilityNodeInfoBuilder() + .setViewIdResourceName(RESOURCE_ID_PREFIX + "child/Id/With/Slash") + .build(); + AccessibilityNodeInfo child3 = + new MockAccessibilityNodeInfoBuilder() + .setViewIdResourceName("childIdWithoutPrefix") + .build(); + AccessibilityNodeInfo parent = + new MockAccessibilityNodeInfoBuilder() + .addChildren(ImmutableList.of(child1, child2, child3)) + .setViewIdResourceName(RESOURCE_ID_PREFIX + "parentId") + .build(); + + assertThat(AccessibilityNodePathBuilder.createNodePath(child1)) + .isEqualTo(PACKAGE_NAME + ":parentId/childId[1]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(child2)) + .isEqualTo(PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(child3)) + .isEqualTo(PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]"); + assertThat(AccessibilityNodePathBuilder.createNodePath(parent)) + .isEqualTo(PACKAGE_NAME + ":parentId"); + } + +} diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java b/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java new file mode 100644 index 000000000000..e363f0c81720 --- /dev/null +++ b/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.accessibility.a11ychecker; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.view.accessibility.AccessibilityNodeInfo; + +import java.util.List; + +final class MockAccessibilityNodeInfoBuilder { + static final String PACKAGE_NAME = "com.example.app"; + private final AccessibilityNodeInfo mMockNodeInfo = mock(AccessibilityNodeInfo.class); + + MockAccessibilityNodeInfoBuilder() { + when(mMockNodeInfo.getPackageName()).thenReturn(PACKAGE_NAME); + } + + MockAccessibilityNodeInfoBuilder setClassName(String className) { + when(mMockNodeInfo.getClassName()).thenReturn(className); + return this; + } + + MockAccessibilityNodeInfoBuilder setViewIdResourceName(String + viewIdResourceName) { + when(mMockNodeInfo.getViewIdResourceName()).thenReturn(viewIdResourceName); + return this; + } + + MockAccessibilityNodeInfoBuilder addChildren(List<AccessibilityNodeInfo> + children) { + when(mMockNodeInfo.getChildCount()).thenReturn(children.size()); + for (int i = 0; i < children.size(); i++) { + when(mMockNodeInfo.getChild(i)).thenReturn(children.get(i)); + when(children.get(i).getParent()).thenReturn(mMockNodeInfo); + } + return this; + } + + AccessibilityNodeInfo build() { + return mMockNodeInfo; + } +} diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS b/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS new file mode 100644 index 000000000000..872a1804555b --- /dev/null +++ b/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS @@ -0,0 +1,5 @@ +# Android Accessibility Framework owners +include /core/java/android/view/accessibility/a11ychecker/OWNERS +include /services/accessibility/OWNERS + +yaraabdullatif@google.com diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 4a4c693d7122..5a4561d7c6ea 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -198,7 +198,7 @@ public class ContentCaptureSessionTest { } @Override - MainContentCaptureSession getMainCaptureSession() { + ContentCaptureSession getMainCaptureSession() { throw new UnsupportedOperationException("should not have been called"); } diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index 1cdcb376effc..b42bcee77c67 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -433,6 +433,72 @@ public class MainContentCaptureSessionTest { assertThat(session.mEvents).isEmpty(); } + @Test + public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.onSessionStarted(0x2, null); + for (int i = 0; i < BUFFER_SIZE - 1; i++) { + View view = prepareView(session); + session.notifyViewAppeared(session.newViewStructure(view)); + } + mTestableLooper.processAllMessages(); + + verify(mMockContentCaptureDirectManager, times(0)) + .sendEvents(any(), anyInt(), any()); + assertThat(session.mEvents).isNull(); + assertThat(session.mEventProcessQueue).hasSize(BUFFER_SIZE - 1); + } + + @Test + public void notifyViewAppearedExactAsMaximumBufferSize() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.onSessionStarted(0x2, null); + for (int i = 0; i < BUFFER_SIZE; i++) { + View view = prepareView(session); + session.notifyViewAppeared(session.newViewStructure(view)); + } + mTestableLooper.processAllMessages(); + + verify(mMockContentCaptureDirectManager, times(1)) + .sendEvents(any(), anyInt(), any()); + assertThat(session.mEvents).isEmpty(); + assertThat(session.mEventProcessQueue).isEmpty(); + } + + @Test + public void notifyViewAppearedAboveMaximumBufferSize() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.onSessionStarted(0x2, null); + for (int i = 0; i < BUFFER_SIZE * 2 + 1; i++) { + View view = prepareView(session); + session.notifyViewAppeared(session.newViewStructure(view)); + } + mTestableLooper.processAllMessages(); + + verify(mMockContentCaptureDirectManager, times(2)) + .sendEvents(any(), anyInt(), any()); + assertThat(session.mEvents).isEmpty(); + assertThat(session.mEventProcessQueue).hasSize(1); + } + /** Simulates the regular content capture events sequence. */ private void notifyContentCaptureEvents(final MainContentCaptureSession session) { final ArrayList<Object> events = new ArrayList<>( @@ -489,11 +555,13 @@ public class MainContentCaptureSessionTest { } private MainContentCaptureSession createSession(ContentCaptureManager manager) { + final Handler testHandler = Handler.createAsync(mTestableLooper.getLooper()); MainContentCaptureSession session = new MainContentCaptureSession( sStrippedContext, manager, - Handler.createAsync(mTestableLooper.getLooper()), + testHandler, + testHandler, mMockSystemServerInterface); session.mComponentName = COMPONENT_NAME; return session; diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java deleted file mode 100644 index 00751288ad62..000000000000 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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.contentcapture; - -import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; -import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED; -import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import android.content.ComponentName; -import android.content.ContentCaptureOptions; -import android.content.Context; -import android.content.pm.ParceledListSlice; -import android.graphics.Insets; -import android.os.Handler; -import android.os.RemoteException; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.util.SparseArray; -import android.view.View; -import android.view.autofill.AutofillId; -import android.view.contentprotection.ContentProtectionEventProcessor; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Test for {@link MainContentCaptureSessionV2}. - * - * <p>Run with: {@code atest - * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionV2Test} - */ -@RunWith(AndroidTestingRunner.class) -@SmallTest -@TestableLooper.RunWithLooper -public class MainContentCaptureSessionV2Test { - - private static final int BUFFER_SIZE = 100; - - private static final int REASON = 123; - - private static final ContentCaptureEvent EVENT = - new ContentCaptureEvent(/* sessionId= */ 0, TYPE_SESSION_STARTED); - - private static final ComponentName COMPONENT_NAME = - new ComponentName("com.test.package", "TestClass"); - - private static final Context sContext = ApplicationProvider.getApplicationContext(); - - private static final ContentCaptureManager.StrippedContext sStrippedContext = - new ContentCaptureManager.StrippedContext(sContext); - - private TestableLooper mTestableLooper; - - @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); - - @Mock private IContentCaptureManager mMockSystemServerInterface; - - @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor; - - @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager; - - @Before - public void setup() { - mTestableLooper = TestableLooper.get(this); - } - - @Test - public void onSessionStarted_contentProtectionEnabled_processorCreated() { - MainContentCaptureSessionV2 session = createSession(); - assertThat(session.mContentProtectionEventProcessor).isNull(); - - session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); - mTestableLooper.processAllMessages(); - - assertThat(session.mContentProtectionEventProcessor).isNotNull(); - } - - @Test - public void onSessionStarted_contentProtectionDisabled_processorNotCreated() { - MainContentCaptureSessionV2 session = - createSession( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ false); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); - mTestableLooper.processAllMessages(); - - assertThat(session.mContentProtectionEventProcessor).isNull(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - } - - @Test - public void onSessionStarted_contentProtectionNoBuffer_processorNotCreated() { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, - -BUFFER_SIZE, - /* requiredGroups= */ List.of(List.of("a")), - /* optionalGroups= */ Collections.emptyList(), - /* optionalGroupsThreshold= */ 0)); - MainContentCaptureSessionV2 session = createSession(options); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); - mTestableLooper.processAllMessages(); - - assertThat(session.mContentProtectionEventProcessor).isNull(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - } - - @Test - public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, - BUFFER_SIZE, - /* requiredGroups= */ Collections.emptyList(), - /* optionalGroups= */ Collections.emptyList(), - /* optionalGroupsThreshold= */ 0)); - MainContentCaptureSessionV2 session = createSession(options); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); - mTestableLooper.processAllMessages(); - - assertThat(session.mContentProtectionEventProcessor).isNull(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - } - - @Test - public void onSessionStarted_noComponentName_processorNotCreated() { - MainContentCaptureSessionV2 session = createSession(); - session.mComponentName = null; - - session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); - mTestableLooper.processAllMessages(); - - assertThat(session.mContentProtectionEventProcessor).isNull(); - } - - @Test - public void sendEvent_contentCaptureDisabled_contentProtectionDisabled() { - MainContentCaptureSessionV2 session = - createSession( - /* enableContentCaptureReceiver= */ false, - /* enableContentProtectionReceiver= */ false); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.sendEvent(EVENT); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mEvents).isNull(); - } - - @Test - public void sendEvent_contentCaptureDisabled_contentProtectionEnabled() { - MainContentCaptureSessionV2 session = - createSession( - /* enableContentCaptureReceiver= */ false, - /* enableContentProtectionReceiver= */ true); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.sendEvent(EVENT); - mTestableLooper.processAllMessages(); - - verify(mMockContentProtectionEventProcessor).processEvent(EVENT); - assertThat(session.mEvents).isNull(); - } - - @Test - public void sendEvent_contentCaptureEnabled_contentProtectionDisabled() { - MainContentCaptureSessionV2 session = - createSession( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ false); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.sendEvent(EVENT); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mEvents).isNotNull(); - assertThat(session.mEvents).containsExactly(EVENT); - } - - @Test - public void sendEvent_contentCaptureEnabled_contentProtectionEnabled() { - MainContentCaptureSessionV2 session = createSession(); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.sendEvent(EVENT); - mTestableLooper.processAllMessages(); - - verify(mMockContentProtectionEventProcessor).processEvent(EVENT); - assertThat(session.mEvents).isNotNull(); - assertThat(session.mEvents).containsExactly(EVENT); - } - - @Test - public void sendEvent_contentProtectionEnabled_processorNotCreated() { - MainContentCaptureSessionV2 session = - createSession( - /* enableContentCaptureReceiver= */ false, - /* enableContentProtectionReceiver= */ true); - - session.sendEvent(EVENT); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mEvents).isNull(); - } - - @Test - public void flush_contentCaptureDisabled_contentProtectionDisabled() throws Exception { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ false, - /* enableContentProtectionReceiver= */ false); - MainContentCaptureSessionV2 session = createSession(options); - session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - - session.flush(REASON); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentProtectionEventProcessor); - verifyZeroInteractions(mMockContentCaptureDirectManager); - assertThat(session.mEvents).containsExactly(EVENT); - } - - @Test - public void flush_contentCaptureDisabled_contentProtectionEnabled() { - MainContentCaptureSessionV2 session = - createSession( - /* enableContentCaptureReceiver= */ false, - /* enableContentProtectionReceiver= */ true); - session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - - session.flush(REASON); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentProtectionEventProcessor); - verifyZeroInteractions(mMockContentCaptureDirectManager); - assertThat(session.mEvents).containsExactly(EVENT); - } - - @Test - public void flush_contentCaptureEnabled_contentProtectionDisabled() throws Exception { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ false); - MainContentCaptureSessionV2 session = createSession(options); - session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - - session.flush(REASON); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mEvents).isEmpty(); - assertEventFlushedContentCapture(options); - } - - @Test - public void flush_contentCaptureEnabled_contentProtectionEnabled() throws Exception { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ true); - MainContentCaptureSessionV2 session = createSession(options); - session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - - session.flush(REASON); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mEvents).isEmpty(); - assertEventFlushedContentCapture(options); - } - - @Test - public void destroySession() throws Exception { - MainContentCaptureSessionV2 session = createSession(); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.destroySession(); - mTestableLooper.processAllMessages(); - - verify(mMockSystemServerInterface).finishSession(anyInt()); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mDirectServiceInterface).isNull(); - assertThat(session.mContentProtectionEventProcessor).isNull(); - } - - @Test - public void resetSession() { - MainContentCaptureSessionV2 session = createSession(); - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - session.resetSession(/* newState= */ 0); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockSystemServerInterface); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mDirectServiceInterface).isNull(); - assertThat(session.mContentProtectionEventProcessor).isNull(); - } - - @Test - @SuppressWarnings("GuardedBy") - public void notifyContentCaptureEvents_notStarted_ContentCaptureDisabled_ProtectionDisabled() { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ false, - /* enableContentProtectionReceiver= */ false); - MainContentCaptureSessionV2 session = createSession(options); - - notifyContentCaptureEvents(session); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentCaptureDirectManager); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mEvents).isNull(); - } - - @Test - @SuppressWarnings("GuardedBy") - public void notifyContentCaptureEvents_started_ContentCaptureDisabled_ProtectionDisabled() { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ false, - /* enableContentProtectionReceiver= */ false); - MainContentCaptureSessionV2 session = createSession(options); - - session.onSessionStarted(0x2, null); - notifyContentCaptureEvents(session); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentCaptureDirectManager); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mEvents).isNull(); - } - - @Test - @SuppressWarnings("GuardedBy") - public void notifyContentCaptureEvents_notStarted_ContentCaptureEnabled_ProtectionEnabled() { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ true); - MainContentCaptureSessionV2 session = createSession(options); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - - notifyContentCaptureEvents(session); - mTestableLooper.processAllMessages(); - - verifyZeroInteractions(mMockContentCaptureDirectManager); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - assertThat(session.mEvents).isNull(); - } - - @Test - @SuppressWarnings("GuardedBy") - public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled() - throws RemoteException { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ true); - MainContentCaptureSessionV2 session = createSession(options); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - - session.onSessionStarted(0x2, null); - // Override the processor for interaction verification. - session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; - notifyContentCaptureEvents(session); - mTestableLooper.processAllMessages(); - - // Force flush will happen twice. - verify(mMockContentCaptureDirectManager, times(1)) - .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARING), any()); - verify(mMockContentCaptureDirectManager, times(1)) - .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARED), any()); - // Other than the five view events, there will be two additional tree appearing events. - verify(mMockContentProtectionEventProcessor, times(7)).processEvent(any()); - assertThat(session.mEvents).isEmpty(); - } - - @Test - public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ true); - MainContentCaptureSessionV2 session = createSession(options); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - - session.onSessionStarted(0x2, null); - for (int i = 0; i < BUFFER_SIZE - 1; i++) { - View view = prepareView(session); - session.notifyViewAppeared(session.newViewStructure(view)); - } - mTestableLooper.processAllMessages(); - - verify(mMockContentCaptureDirectManager, times(0)) - .sendEvents(any(), anyInt(), any()); - assertThat(session.mEvents).isNull(); - assertThat(session.mEventProcessQueue).hasSize(BUFFER_SIZE - 1); - } - - @Test - public void notifyViewAppearedExactAsMaximumBufferSize() throws RemoteException { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ true); - MainContentCaptureSessionV2 session = createSession(options); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - - session.onSessionStarted(0x2, null); - for (int i = 0; i < BUFFER_SIZE; i++) { - View view = prepareView(session); - session.notifyViewAppeared(session.newViewStructure(view)); - } - mTestableLooper.processAllMessages(); - - verify(mMockContentCaptureDirectManager, times(1)) - .sendEvents(any(), anyInt(), any()); - assertThat(session.mEvents).isEmpty(); - assertThat(session.mEventProcessQueue).isEmpty(); - } - - @Test - public void notifyViewAppearedAboveMaximumBufferSize() throws RemoteException { - ContentCaptureOptions options = - createOptions( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ true); - MainContentCaptureSessionV2 session = createSession(options); - session.mDirectServiceInterface = mMockContentCaptureDirectManager; - - session.onSessionStarted(0x2, null); - for (int i = 0; i < BUFFER_SIZE * 2 + 1; i++) { - View view = prepareView(session); - session.notifyViewAppeared(session.newViewStructure(view)); - } - mTestableLooper.processAllMessages(); - - verify(mMockContentCaptureDirectManager, times(2)) - .sendEvents(any(), anyInt(), any()); - assertThat(session.mEvents).isEmpty(); - assertThat(session.mEventProcessQueue).hasSize(1); - } - - /** Simulates the regular content capture events sequence. */ - private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) { - final ArrayList<Object> events = new ArrayList<>( - List.of( - prepareView(session), - prepareView(session), - new AutofillId(0), - prepareView(session), - Insets.of(0, 0, 0, 0) - ) - ); - - final SparseArray<ArrayList<Object>> contentCaptureEvents = new SparseArray<>(); - contentCaptureEvents.set(session.getId(), events); - - session.notifyContentCaptureEvents(contentCaptureEvents); - } - - private View prepareView(final MainContentCaptureSessionV2 session) { - final View view = new View(sContext); - view.setContentCaptureSession(session); - return view; - } - - private static ContentCaptureOptions createOptions( - boolean enableContentCaptureReceiver, - ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) { - return new ContentCaptureOptions( - /* loggingLevel= */ 0, - BUFFER_SIZE, - /* idleFlushingFrequencyMs= */ 0, - /* textChangeFlushingFrequencyMs= */ 0, - /* logHistorySize= */ 0, - /* disableFlushForViewTreeAppearing= */ false, - enableContentCaptureReceiver, - contentProtectionOptions, - /* whitelistedComponents= */ null); - } - - private static ContentCaptureOptions createOptions( - boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) { - return createOptions( - enableContentCaptureReceiver, - new ContentCaptureOptions.ContentProtectionOptions( - enableContentProtectionReceiver, - BUFFER_SIZE, - /* requiredGroups= */ List.of(List.of("a")), - /* optionalGroups= */ Collections.emptyList(), - /* optionalGroupsThreshold= */ 0)); - } - - private ContentCaptureManager createManager(ContentCaptureOptions options) { - return new ContentCaptureManager(sContext, mMockSystemServerInterface, options); - } - - private MainContentCaptureSessionV2 createSession(ContentCaptureManager manager) { - final Handler testHandler = Handler.createAsync(mTestableLooper.getLooper()); - MainContentCaptureSessionV2 session = - new MainContentCaptureSessionV2( - sStrippedContext, - manager, - testHandler, - testHandler, - mMockSystemServerInterface); - session.mComponentName = COMPONENT_NAME; - return session; - } - - private MainContentCaptureSessionV2 createSession(ContentCaptureOptions options) { - return createSession(createManager(options)); - } - - private MainContentCaptureSessionV2 createSession( - boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) { - return createSession( - createOptions(enableContentCaptureReceiver, enableContentProtectionReceiver)); - } - - private MainContentCaptureSessionV2 createSession() { - return createSession( - /* enableContentCaptureReceiver= */ true, - /* enableContentProtectionReceiver= */ true); - } - - private void assertEventFlushedContentCapture(ContentCaptureOptions options) throws Exception { - ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class); - verify(mMockContentCaptureDirectManager) - .sendEvents(captor.capture(), eq(REASON), eq(options)); - - assertThat(captor.getValue()).isNotNull(); - List<ContentCaptureEvent> actual = captor.getValue().getList(); - assertThat(actual).isNotNull(); - assertThat(actual).containsExactly(EVENT); - } -} diff --git a/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java b/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java new file mode 100644 index 000000000000..8e9ba7b008cd --- /dev/null +++ b/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.content.Context; +import android.util.SizeF; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; +import android.view.View; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.util.Map; + +/** + * Tests for RemoteViews. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RemoteViewsProtoTest { + + // This can point to any other package which exists on the device. + private static final String OTHER_PACKAGE = "com.android.systemui"; + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + private Context mContext; + private String mPackage; + private LinearLayout mContainer; + + @Before + public void setup() { + mContext = InstrumentationRegistry.getContext(); + mPackage = mContext.getPackageName(); + mContainer = new LinearLayout(mContext); + } + + @Test + public void copy_canStillBeApplied() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + + RemoteViews clone = recreateFromProto(original); + + clone.apply(mContext, mContainer); + } + + @SuppressWarnings("ReturnValueIgnored") + @Test + public void clone_repeatedly() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + + recreateFromProto(original); + recreateFromProto(original); + + original.apply(mContext, mContainer); + } + + @Test + public void clone_chained() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + + RemoteViews clone = recreateFromProto(recreateFromProto(original)); + + + clone.apply(mContext, mContainer); + } + + @Test + public void landscapePortraitViews_lightBackgroundLayoutFlag() { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + + RemoteViews parent = new RemoteViews(inner, inner); + parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT); + + View view = recreateFromProto(parent).apply(mContext, mContainer); + assertNull(view.findViewById(R.id.text)); + assertNotNull(view.findViewById(R.id.light_background_text)); + } + + @Test + public void sizedViews_lightBackgroundLayoutFlag() { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + + RemoteViews parent = new RemoteViews( + Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner)); + parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT); + + View view = recreateFromProto(parent).apply(mContext, mContainer); + assertNull(view.findViewById(R.id.text)); + assertNotNull(view.findViewById(R.id.light_background_text)); + } + + @Test + public void nestedLandscapeViews() throws Exception { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + for (int i = 0; i < 10; i++) { + views = new RemoteViews(views, new RemoteViews(mPackage, R.layout.remote_views_test)); + } + // writeTo/createFromProto works + recreateFromProto(views); + + views = new RemoteViews(mPackage, R.layout.remote_views_test); + for (int i = 0; i < 11; i++) { + views = new RemoteViews(views, new RemoteViews(mPackage, R.layout.remote_views_test)); + } + // writeTo/createFromProto fails + exception.expect(IllegalArgumentException.class); + recreateFromProtoNoRethrow(views); + } + + private RemoteViews recreateFromProto(RemoteViews views) { + try { + return recreateFromProtoNoRethrow(views); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private RemoteViews recreateFromProtoNoRethrow(RemoteViews views) throws Exception { + ProtoOutputStream out = new ProtoOutputStream(); + views.writePreviewToProto(mContext, out); + ProtoInputStream in = new ProtoInputStream(out.getBytes()); + return RemoteViews.createPreviewFromProto(mContext, in); + } +} diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index b0190a5c8218..d4482f243939 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -112,7 +112,7 @@ public class WindowOnBackInvokedDispatcherTest { doReturn(mApplicationInfo).when(mContext).getApplicationInfo(); mDispatcher = new WindowOnBackInvokedDispatcher(mContext, Looper.getMainLooper()); - mDispatcher.attachToWindow(mWindowSession, mWindow, mImeBackAnimationController); + mDispatcher.attachToWindow(mWindowSession, mWindow, null, mImeBackAnimationController); } private void waitForIdle() { @@ -455,25 +455,26 @@ public class WindowOnBackInvokedDispatcherTest { @Test public void registerImeCallbacks_onBackInvokedCallbackEnabled() throws RemoteException { - mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback); + verifyImeCallackRegistrations(); + } + + @Test + public void registerImeCallbacks_onBackInvokedCallbackDisabled() throws RemoteException { + doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled(); + verifyImeCallackRegistrations(); + } + + private void verifyImeCallackRegistrations() throws RemoteException { + // verify default callback is replaced with ImeBackAnimationController + mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT); assertCallbacksSize(/* default */ 1, /* overlay */ 0); assertSetCallbackInfo(); assertTopCallback(mImeBackAnimationController); - mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback); + // verify regular ime callback is successfully registered + mDispatcher.registerOnBackInvokedCallbackUnchecked(mImeCallback, PRIORITY_DEFAULT); assertCallbacksSize(/* default */ 2, /* overlay */ 0); assertSetCallbackInfo(); assertTopCallback(mImeCallback); } - - @Test - public void registerImeCallbacks_legacyBack() throws RemoteException { - doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled(); - - mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback); - assertNoSetCallbackInfo(); - - mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback); - assertNoSetCallbackInfo(); - } } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java index 708f246fea75..928fce9e6e59 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java @@ -25,33 +25,28 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.UserIdInt; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; -import android.content.ContextWrapper; import android.content.pm.ParceledListSlice; import android.os.Handler; import android.os.RemoteException; -import android.os.UserHandle; import android.provider.Settings; +import android.testing.TestableContext; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.internal.accessibility.TestUtils; import com.android.internal.accessibility.common.ShortcutConstants; -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.internal.util.test.FakeSettingsProviderRule; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -79,39 +74,33 @@ public class ShortcutUtilsTest { private static final String STANDARD_SERVICE_COMPONENT_NAME = "fake.package/fake.standard.service.name"; private static final String SERVICE_NAME_SUMMARY = "Summary"; - - @Rule - public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); @Mock private IAccessibilityManager mAccessibilityManagerService; - private ContextWrapper mContextSpy; + private TestableContext mContext; + @UserIdInt + private int mDefaultUserId; @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - mContextSpy = spy( - new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())); - - ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); - when(mContextSpy.getContentResolver()).thenReturn(contentResolver); + mContext = new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); + mDefaultUserId = mContext.getContentResolver().getUserId(); AccessibilityManager accessibilityManager = new AccessibilityManager( - mContextSpy, mock(Handler.class), - mAccessibilityManagerService, UserHandle.myUserId(), + mContext, mock(Handler.class), + mAccessibilityManagerService, mDefaultUserId, /* serviceConnect= */ true); - when(mContextSpy.getSystemService(Context.ACCESSIBILITY_SERVICE)) - .thenReturn(accessibilityManager); - - setupFakeA11yServiceInfos(); + mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, accessibilityManager); + setupFakeInstalledA11yServiceInfos(); } @Test public void getShortcutTargets_softwareShortcutNoService_emptyResult() { assertThat( ShortcutUtils.getShortcutTargetsFromSettings( - mContextSpy, - ShortcutConstants.UserShortcutType.SOFTWARE, UserHandle.myUserId()) + mContext, + ShortcutConstants.UserShortcutType.SOFTWARE, mDefaultUserId) ).isEmpty(); } @@ -119,8 +108,8 @@ public class ShortcutUtilsTest { public void getShortcutTargets_volumeKeyShortcutNoService_emptyResult() { assertThat( ShortcutUtils.getShortcutTargetsFromSettings( - mContextSpy, ShortcutConstants.UserShortcutType.HARDWARE, - UserHandle.myUserId()) + mContext, ShortcutConstants.UserShortcutType.HARDWARE, + mDefaultUserId) ).isEmpty(); } @@ -131,8 +120,8 @@ public class ShortcutUtilsTest { assertThat( ShortcutUtils.getShortcutTargetsFromSettings( - mContextSpy, ShortcutConstants.UserShortcutType.SOFTWARE, - UserHandle.myUserId()) + mContext, ShortcutConstants.UserShortcutType.SOFTWARE, + mDefaultUserId) ).containsExactlyElementsIn(ONE_COMPONENT); } @@ -143,8 +132,8 @@ public class ShortcutUtilsTest { assertThat( ShortcutUtils.getShortcutTargetsFromSettings( - mContextSpy, ShortcutConstants.UserShortcutType.HARDWARE, - UserHandle.myUserId()) + mContext, ShortcutConstants.UserShortcutType.HARDWARE, + mDefaultUserId) ).containsExactlyElementsIn(TWO_COMPONENTS); } @@ -156,8 +145,8 @@ public class ShortcutUtilsTest { assertThat( ShortcutUtils.getShortcutTargetsFromSettings( - mContextSpy, ShortcutConstants.UserShortcutType.TRIPLETAP, - UserHandle.myUserId()) + mContext, ShortcutConstants.UserShortcutType.TRIPLETAP, + mDefaultUserId) ).isEmpty(); } @@ -169,8 +158,8 @@ public class ShortcutUtilsTest { assertThat( ShortcutUtils.getShortcutTargetsFromSettings( - mContextSpy, ShortcutConstants.UserShortcutType.TRIPLETAP, - UserHandle.myUserId()) + mContext, ShortcutConstants.UserShortcutType.TRIPLETAP, + mDefaultUserId) ).containsExactly(ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER); } @@ -180,23 +169,46 @@ public class ShortcutUtilsTest { ALWAYS_ON_SERVICE_COMPONENT_NAME, /* serviceOn= */ true, /* shortcutOn= */ false); ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContextSpy, + mContext, Set.of(ALWAYS_ON_SERVICE_COMPONENT_NAME), - UserHandle.myUserId() + mDefaultUserId ); assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ false); } @Test + public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOnForBothUsers_noShortcutsForGuestUser_serviceTurnedOffForGuestUserOnly() { + // setup arbitrary userId by add 10 to the default user id + final int guestUserId = mDefaultUserId + 10; + setupA11yServiceAndShortcutStateForUser( + ALWAYS_ON_SERVICE_COMPONENT_NAME, /* serviceOn= */ true, + /* shortcutOn= */ true, mDefaultUserId); + setupA11yServiceAndShortcutStateForUser( + ALWAYS_ON_SERVICE_COMPONENT_NAME, /* serviceOn= */ true, + /* shortcutOn= */ false, guestUserId); + + ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( + mContext, + Set.of(ALWAYS_ON_SERVICE_COMPONENT_NAME), + guestUserId + ); + + assertA11yServiceStateForUser( + ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ false, guestUserId); + assertA11yServiceStateForUser( + ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true, mDefaultUserId); + } + + @Test public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOn_hasShortcut_serviceKeepsOn() { setupA11yServiceAndShortcutState( ALWAYS_ON_SERVICE_COMPONENT_NAME, /* serviceOn= */ true, /* shortcutOn= */ true); ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContextSpy, + mContext, Set.of(ALWAYS_ON_SERVICE_COMPONENT_NAME), - UserHandle.myUserId() + mDefaultUserId ); assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true); @@ -208,9 +220,9 @@ public class ShortcutUtilsTest { ALWAYS_ON_SERVICE_COMPONENT_NAME, /* serviceOn= */ false, /* shortcutOn= */ false); ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContextSpy, + mContext, Set.of(ALWAYS_ON_SERVICE_COMPONENT_NAME), - UserHandle.myUserId() + mDefaultUserId ); assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ false); @@ -222,9 +234,9 @@ public class ShortcutUtilsTest { ALWAYS_ON_SERVICE_COMPONENT_NAME, /* serviceOn= */ false, /* shortcutOn= */ true); ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContextSpy, + mContext, Set.of(ALWAYS_ON_SERVICE_COMPONENT_NAME), - UserHandle.myUserId() + mDefaultUserId ); assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true); @@ -236,9 +248,9 @@ public class ShortcutUtilsTest { STANDARD_SERVICE_COMPONENT_NAME, /* serviceOn= */ true, /* shortcutOn= */ false); ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContextSpy, + mContext, Set.of(STANDARD_SERVICE_COMPONENT_NAME), - UserHandle.myUserId() + mDefaultUserId ); assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ true); @@ -250,9 +262,9 @@ public class ShortcutUtilsTest { STANDARD_SERVICE_COMPONENT_NAME, /* serviceOn= */ true, /* shortcutOn= */ true); ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContextSpy, + mContext, Set.of(STANDARD_SERVICE_COMPONENT_NAME), - UserHandle.myUserId() + mDefaultUserId ); assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ true); @@ -264,9 +276,9 @@ public class ShortcutUtilsTest { STANDARD_SERVICE_COMPONENT_NAME, /* serviceOn= */ false, /* shortcutOn= */ false); ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContextSpy, + mContext, Set.of(STANDARD_SERVICE_COMPONENT_NAME), - UserHandle.myUserId() + mDefaultUserId ); assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ false); @@ -278,9 +290,9 @@ public class ShortcutUtilsTest { STANDARD_SERVICE_COMPONENT_NAME, /* serviceOn= */ false, /* shortcutOn= */ true); ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContextSpy, + mContext, Set.of(STANDARD_SERVICE_COMPONENT_NAME), - UserHandle.myUserId() + mDefaultUserId ); assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ false); @@ -292,18 +304,18 @@ public class ShortcutUtilsTest { stringJoiner.add(target); } Settings.Secure.putStringForUser( - mContextSpy.getContentResolver(), shortcutSettingsKey, + mContext.getContentResolver(), shortcutSettingsKey, stringJoiner.toString(), - UserHandle.myUserId()); + mDefaultUserId); } private void enableTripleTapShortcutForMagnification(boolean enable) { Settings.Secure.putInt( - mContextSpy.getContentResolver(), + mContext.getContentResolver(), ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, enable ? 1 : 0); } - private void setupFakeA11yServiceInfos() throws RemoteException { + private void setupFakeInstalledA11yServiceInfos() throws RemoteException { List<AccessibilityServiceInfo> serviceInfos = List.of( TestUtils.createFakeServiceInfo( ALWAYS_ON_SERVICE_PACKAGE_LABEL, @@ -322,37 +334,55 @@ public class ShortcutUtilsTest { private void setupA11yServiceAndShortcutState( String a11yServiceComponentName, boolean serviceOn, boolean shortcutOn) { - enableA11yService(a11yServiceComponentName, serviceOn); - addShortcutForA11yService(a11yServiceComponentName, shortcutOn); + setupA11yServiceAndShortcutStateForUser( + a11yServiceComponentName, serviceOn, shortcutOn, mDefaultUserId); + } + + private void setupA11yServiceAndShortcutStateForUser( + String a11yServiceComponentName, boolean serviceOn, + boolean shortcutOn, @UserIdInt int userId) { + enableA11yServiceForUser(a11yServiceComponentName, serviceOn, userId); + addShortcutForA11yServiceForUser(a11yServiceComponentName, shortcutOn, userId); } private void assertA11yServiceState(String a11yServiceComponentName, boolean enabled) { + assertA11yServiceStateForUser(a11yServiceComponentName, enabled, mDefaultUserId); + } + + private void assertA11yServiceStateForUser( + String a11yServiceComponentName, boolean enabled, @UserIdInt int userId) { if (enabled) { assertThat( - Settings.Secure.getString( - mContextSpy.getContentResolver(), - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) + Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userId) ).contains(a11yServiceComponentName); } else { assertThat( - Settings.Secure.getString( - mContextSpy.getContentResolver(), - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) + Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userId) ).doesNotContain(a11yServiceComponentName); } } - private void enableA11yService(String a11yServiceComponentName, boolean enable) { - Settings.Secure.putString( - mContextSpy.getContentResolver(), + private void enableA11yServiceForUser( + String a11yServiceComponentName, boolean enable, @UserIdInt int userId) { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - enable ? a11yServiceComponentName : ""); + enable ? a11yServiceComponentName : "", + userId); } - private void addShortcutForA11yService(String a11yServiceComponentName, boolean add) { - Settings.Secure.putString( - mContextSpy.getContentResolver(), + private void addShortcutForA11yServiceForUser( + String a11yServiceComponentName, boolean add, @UserIdInt int userId) { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, - add ? a11yServiceComponentName : ""); + add ? a11yServiceComponentName : "", + userId); } } diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java index ac659e1bc593..6c6feaf4ae9c 100644 --- a/core/tests/utiltests/src/android/util/TimeUtilsTest.java +++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java @@ -18,17 +18,19 @@ package android.util; import static org.junit.Assert.assertEquals; -import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.runner.AndroidJUnit4; +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.TimeZone; import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) @@ -42,6 +44,22 @@ public class TimeUtilsTest { public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; + private TimeZone mOrigTimezone; + + @Before + public void setUp() { + mOrigTimezone = TimeZone.getDefault(); + + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + @After + public void tearDown() { + if (mOrigTimezone != null) { + TimeZone.setDefault(mOrigTimezone); + } + } + @Test public void testFormatTime() { assertEquals("1672556400000 (now)", @@ -85,32 +103,29 @@ public class TimeUtilsTest { } @Test - @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testDumpTime() { - assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> { + assertEquals("2023-01-01 07:00:00.000", runWithPrintWriter((pw) -> { TimeUtils.dumpTime(pw, 1672556400000L); })); - assertEquals("2023-01-01 00:00:00.000 (now)", runWithPrintWriter((pw) -> { + assertEquals("2023-01-01 07:00:00.000 (now)", runWithPrintWriter((pw) -> { TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L); })); - assertEquals("2023-01-01 00:00:00.000 (-10ms)", runWithPrintWriter((pw) -> { + assertEquals("2023-01-01 07:00:00.000 (-10ms)", runWithPrintWriter((pw) -> { TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L + 10); })); } @Test - @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testFormatForLogging() { assertEquals("unknown", TimeUtils.formatForLogging(0)); assertEquals("unknown", TimeUtils.formatForLogging(-1)); assertEquals("unknown", TimeUtils.formatForLogging(Long.MIN_VALUE)); - assertEquals("2023-01-01 00:00:00", TimeUtils.formatForLogging(1672556400000L)); + assertEquals("2023-01-01 07:00:00", TimeUtils.formatForLogging(1672556400000L)); } @Test - @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testLogTimeOfDay() { - assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L)); + assertEquals("01-01 07:00:00.000", TimeUtils.logTimeOfDay(1672556400000L)); } public static String runWithPrintWriter(Consumer<PrintWriter> consumer) { diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java index 94298dcd2c74..83a8f8f91ae6 100644 --- a/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/persistence/ParsedVibrationTest.java @@ -63,6 +63,34 @@ public class ParsedVibrationTest { } @Test + public void testEquals() { + assertThat(new ParsedVibration(List.of())).isEqualTo(new ParsedVibration(List.of())); + assertThat(new ParsedVibration(List.of())).isNotEqualTo(new ParsedVibration(mEffect1)); + assertThat(new ParsedVibration(mEffect1)).isEqualTo(new ParsedVibration(mEffect1)); + assertThat(new ParsedVibration(mEffect1)).isNotEqualTo(new ParsedVibration(mEffect2)); + assertThat(new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3))) + .isEqualTo(new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3))); + assertThat(new ParsedVibration(List.of(mEffect1, mEffect2))) + .isNotEqualTo(new ParsedVibration(List.of(mEffect2, mEffect1))); + } + + @Test + public void testHashCode() { + assertThat(new ParsedVibration(mEffect1).hashCode()) + .isEqualTo(new ParsedVibration(mEffect1).hashCode()); + assertThat(new ParsedVibration(mEffect1).hashCode()) + .isNotEqualTo(new ParsedVibration(mEffect2).hashCode()); + assertThat(new ParsedVibration(List.of()).hashCode()) + .isEqualTo(new ParsedVibration(List.of()).hashCode()); + assertThat(new ParsedVibration(List.of()).hashCode()) + .isNotEqualTo(new ParsedVibration(mEffect1).hashCode()); + assertThat(new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3)).hashCode()) + .isEqualTo(new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3)).hashCode()); + assertThat(new ParsedVibration(List.of(mEffect1, mEffect2)).hashCode()) + .isNotEqualTo(new ParsedVibration(List.of(mEffect2, mEffect1)).hashCode()); + } + + @Test public void testResolve_allUnsupportedVibrations() { when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(false); @@ -91,21 +119,6 @@ public class ParsedVibrationTest { .isEqualTo(mEffect1); } - @Test - public void testGetVibrationEffects() { - ParsedVibration parsedVibration = - new ParsedVibration(List.of(mEffect1, mEffect2, mEffect3)); - assertThat(parsedVibration.getVibrationEffects()) - .containsExactly(mEffect1, mEffect2, mEffect3) - .inOrder(); - - parsedVibration = new ParsedVibration(List.of(mEffect1)); - assertThat(parsedVibration.getVibrationEffects()).containsExactly(mEffect1); - - parsedVibration = new ParsedVibration(List.of()); - assertThat(parsedVibration.getVibrationEffects()).isEmpty(); - } - private Subject assertThatResolution( Vibrator vibrator, List<VibrationEffect> componentVibrations) { return assertThat(new ParsedVibration(componentVibrations).resolve(vibrator)); diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java index 7d8c53faaec4..bf9a820aca5c 100644 --- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java @@ -37,9 +37,9 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.xmlpull.v1.XmlPullParser; -import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -74,18 +74,22 @@ public class VibrationEffectXmlSerializationTest { .addPrimitive(PRIMITIVE_CLICK) .addPrimitive(PRIMITIVE_TICK, 0.2497f) .compose(); - String xml = "<vibration-effect>" - + "<primitive-effect name=\"click\"/>" - + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>" - + "</vibration-effect>"; + String xml = """ + <vibration-effect> + <primitive-effect name="click"/> + <primitive-effect name="tick" scale="0.2497"/> + </vibration-effect> + """.trim(); VibrationEffect effect2 = VibrationEffect.startComposition() .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) .compose(); - String xml2 = "<vibration-effect>" - + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>" - + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>" - + "</vibration-effect>"; + String xml2 = """ + <vibration-effect> + <primitive-effect name="low_tick" delayMs="356"/> + <primitive-effect name="spin" scale="0.6364" delayMs="7"/> + </vibration-effect> + """.trim(); TypedXmlPullParser parser = createXmlPullParser(xml); assertParseElementSucceeds(parser, effect); @@ -114,7 +118,12 @@ public class VibrationEffectXmlSerializationTest { assertEndOfDocument(parser); // Check when there is comment before the end tag. - xml = "<vibration-effect><primitive-effect name=\"tick\"/><!-- hi --></vibration-effect>"; + xml = """ + <vibration-effect> + <primitive-effect name="tick"/> + <!-- hi --> + </vibration-effect> + """.trim(); parser = createXmlPullParser(xml); assertParseElementSucceeds( parser, VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK).compose()); @@ -128,18 +137,22 @@ public class VibrationEffectXmlSerializationTest { .addPrimitive(PRIMITIVE_CLICK) .addPrimitive(PRIMITIVE_TICK, 0.2497f) .compose(); - String vibrationXml1 = "<vibration-effect>" - + "<primitive-effect name=\"click\"/>" - + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>" - + "</vibration-effect>"; + String vibrationXml1 = """ + <vibration-effect> + <primitive-effect name="click"/> + <primitive-effect name="tick" scale="0.2497"/> + </vibration-effect> + """.trim(); VibrationEffect effect2 = VibrationEffect.startComposition() .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) .compose(); - String vibrationXml2 = "<vibration-effect>" - + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>" - + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>" - + "</vibration-effect>"; + String vibrationXml2 = """ + <vibration-effect> + <primitive-effect name="low_tick" delayMs="356"/> + <primitive-effect name="spin" scale="0.6364" delayMs="7"/> + </vibration-effect> + """.trim(); String xml = "<vibration-select>" + vibrationXml1 + vibrationXml2 + "</vibration-select>"; TypedXmlPullParser parser = createXmlPullParser(xml); @@ -183,8 +196,11 @@ public class VibrationEffectXmlSerializationTest { @Test public void testParseElement_withHiddenApis_onlySucceedsWithFlag() throws Exception { // Check when the root tag is "vibration". - String xml = - "<vibration-effect><predefined-effect name=\"texture_tick\"/></vibration-effect>"; + String xml = """ + <vibration-effect> + <predefined-effect name="texture_tick"/> + </vibration-effect> + """.trim(); assertParseElementSucceeds(createXmlPullParser(xml), VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS, VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK)); @@ -199,131 +215,186 @@ public class VibrationEffectXmlSerializationTest { } @Test - public void testParseElement_badXml_throwsException() throws Exception { + public void testParseElement_badXml_throwsException() { // No "vibration-select" tag. - assertParseElementFails( - "<vibration-effect>rand text<primitive-effect name=\"click\"/></vibration-effect>"); - assertParseElementFails("<bad-tag><primitive-effect name=\"click\"/></vibration-effect>"); - assertParseElementFails("<primitive-effect name=\"click\"/></vibration-effect>"); - assertParseElementFails("<vibration-effect><primitive-effect name=\"click\"/>"); + assertParseElementFails(""" + <vibration-effect> + rand text + <primitive-effect name="click"/> + </vibration-effect> + """); + assertParseElementFails(""" + <bad-tag> + <primitive-effect name="click"/> + </vibration-effect> + """); + assertParseElementFails(""" + <primitive-effect name="click"/> + </vibration-effect> + """); + assertParseElementFails(""" + <vibration-effect> + <primitive-effect name="click"/> + """); // Incomplete XML. - assertParseElementFails("<vibration-select><primitive-effect name=\"click\"/>"); - assertParseElementFails("<vibration-select>" - + "<vibration-effect>" - + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>" - + "</vibration-effect>"); + assertParseElementFails(""" + <vibration-select> + <primitive-effect name="click"/> + """); + assertParseElementFails(""" + <vibration-select> + <vibration-effect> + <primitive-effect name="low_tick" delayMs="356"/> + </vibration-effect> + """); // Bad vibration XML. - assertParseElementFails("<vibration-select>" - + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>" - + "</vibration-effect>" - + "</vibration-select>"); + assertParseElementFails(""" + <vibration-select> + <primitive-effect name="low_tick" delayMs="356"/> + </vibration-effect> + </vibration-select> + """); // "vibration-select" tag should have no attributes. - assertParseElementFails("<vibration-select bad_attr=\"123\">" - + "<vibration-effect>" - + "<predefined-effect name=\"tick\"/>" - + "</vibration-effect>" - + "</vibration-select>"); + assertParseElementFails(""" + <vibration-select bad_attr="123"> + <vibration-effect> + <predefined-effect name="tick"/> + </vibration-effect> + </vibration-select> + """); } @Test - public void testPrimitives_allSucceed() throws IOException { + public void testInvalidEffects_allFail() { + // Invalid root tag. + String xml = """ + <vibration> + <predefined-effect name="click"/> + </vibration> + """; + + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + // Invalid effect name. + xml = """ + <vibration-effect> + <predefined-effect name="invalid"/> + </vibration-effect> + """; + + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + } + + @Test + public void testVibrationSelectTag_onlyParseDocumentSucceeds() throws Exception { + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + String xml = """ + <vibration-select> + <vibration-effect><predefined-effect name="click"/></vibration-effect> + </vibration-select> + """; + + assertPublicApisParseDocumentSucceeds(xml, effect); + assertHiddenApisParseDocumentSucceeds(xml, effect); + + assertPublicApisParseVibrationEffectFails(xml); + assertHiddenApisParseVibrationEffectFails(xml); + } + + @Test + public void testPrimitives_allSucceed() throws Exception { VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(PRIMITIVE_CLICK) .addPrimitive(PRIMITIVE_TICK, 0.2497f) .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) .compose(); - String xml = "<vibration-effect>" - + "<primitive-effect name=\"click\"/>" - + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>" - + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>" - + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>" - + "</vibration-effect>"; + String xml = """ + <vibration-effect> + <primitive-effect name="click"/> + <primitive-effect name="tick" scale="0.2497"/> + <primitive-effect name="low_tick" delayMs="356"/> + <primitive-effect name="spin" scale="0.6364" delayMs="7"/> + </vibration-effect> + """; assertPublicApisParserSucceeds(xml, effect); assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin"); assertPublicApisRoundTrip(effect); - assertHiddenApisParseVibrationEffectSucceeds(xml, effect); + assertHiddenApisParserSucceeds(xml, effect); assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin"); assertHiddenApisRoundTrip(effect); } @Test - public void testParseDocument_withVibrationSelectTag_withHiddenApis_onlySucceedsWithFlag() - throws Exception { - // Check when the root tag is "vibration-effect". - String xml = - "<vibration-effect><predefined-effect name=\"texture_tick\"/></vibration-effect>"; - assertParseDocumentSucceeds(xml, - VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS, - VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK)); - assertThat(parseDocument(xml, /* flags= */ 0)).isNull(); - - // Check when the root tag is "vibration-select". - xml = "<vibration-select>" + xml + "</vibration-select>"; - assertParseDocumentSucceeds(xml, - VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS, - VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK)); - assertThat(parseDocument(xml, /* flags= */ 0)).isNull(); - } - - @Test - public void testWaveforms_allSucceed() throws IOException { + public void testWaveforms_allSucceed() throws Exception { VibrationEffect effect = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0}, new int[]{254, 1, 255, 0}, /* repeat= */ 0); - String xml = "<vibration-effect>" - + "<waveform-effect><repeating>" - + "<waveform-entry durationMs=\"123\" amplitude=\"254\"/>" - + "<waveform-entry durationMs=\"456\" amplitude=\"1\"/>" - + "<waveform-entry durationMs=\"789\" amplitude=\"255\"/>" - + "<waveform-entry durationMs=\"0\" amplitude=\"0\"/>" - + "</repeating></waveform-effect>" - + "</vibration-effect>"; + String xml = """ + <vibration-effect> + <waveform-effect> + <repeating> + <waveform-entry durationMs="123" amplitude="254"/> + <waveform-entry durationMs="456" amplitude="1"/> + <waveform-entry durationMs="789" amplitude="255"/> + <waveform-entry durationMs="0" amplitude="0"/> + </repeating> + </waveform-effect> + </vibration-effect> + """; assertPublicApisParserSucceeds(xml, effect); assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0"); assertPublicApisRoundTrip(effect); - assertHiddenApisParseVibrationEffectSucceeds(xml, effect); + assertHiddenApisParserSucceeds(xml, effect); assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0"); assertHiddenApisRoundTrip(effect); } @Test public void testPredefinedEffects_publicEffectsWithDefaultFallback_allSucceed() - throws IOException { + throws Exception { for (Map.Entry<String, Integer> entry : createPublicPredefinedEffectsMap().entrySet()) { VibrationEffect effect = VibrationEffect.get(entry.getValue()); - String xml = String.format( - "<vibration-effect><predefined-effect name=\"%s\"/></vibration-effect>", + String xml = String.format(""" + <vibration-effect> + <predefined-effect name="%s"/> + </vibration-effect> + """, entry.getKey()); assertPublicApisParserSucceeds(xml, effect); assertPublicApisSerializerSucceeds(effect, entry.getKey()); assertPublicApisRoundTrip(effect); - assertHiddenApisParseVibrationEffectSucceeds(xml, effect); + assertHiddenApisParserSucceeds(xml, effect); assertHiddenApisSerializerSucceeds(effect, entry.getKey()); assertHiddenApisRoundTrip(effect); } } @Test - public void testPredefinedEffects_hiddenEffects_onlySucceedsWithFlag() throws IOException { + public void testPredefinedEffects_hiddenEffects_onlySucceedsWithFlag() throws Exception { for (Map.Entry<String, Integer> entry : createHiddenPredefinedEffectsMap().entrySet()) { VibrationEffect effect = VibrationEffect.get(entry.getValue()); - String xml = String.format( - "<vibration-effect><predefined-effect name=\"%s\"/></vibration-effect>", + String xml = String.format(""" + <vibration-effect> + <predefined-effect name="%s"/> + </vibration-effect> + """, entry.getKey()); assertPublicApisParserFails(xml); assertPublicApisSerializerFails(effect); - assertHiddenApisParseVibrationEffectSucceeds(xml, effect); + assertHiddenApisParserSucceeds(xml, effect); assertHiddenApisSerializerSucceeds(effect, entry.getKey()); assertHiddenApisRoundTrip(effect); } @@ -331,33 +402,119 @@ public class VibrationEffectXmlSerializationTest { @Test public void testPredefinedEffects_allEffectsWithNonDefaultFallback_onlySucceedsWithFlag() - throws IOException { + throws Exception { for (Map.Entry<String, Integer> entry : createAllPredefinedEffectsMap().entrySet()) { boolean nonDefaultFallback = !PrebakedSegment.DEFAULT_SHOULD_FALLBACK; VibrationEffect effect = VibrationEffect.get(entry.getValue(), nonDefaultFallback); - String xml = String.format( - "<vibration-effect><predefined-effect name=\"%s\" fallback=\"%s\"/>" - + "</vibration-effect>", + String xml = String.format(""" + <vibration-effect> + <predefined-effect name="%s" fallback="%s"/> + </vibration-effect> + """, entry.getKey(), nonDefaultFallback); assertPublicApisParserFails(xml); assertPublicApisSerializerFails(effect); - assertHiddenApisParseVibrationEffectSucceeds(xml, effect); + assertHiddenApisParserSucceeds(xml, effect); assertHiddenApisSerializerSucceeds(effect, entry.getKey()); assertHiddenApisRoundTrip(effect); } } - private void assertPublicApisParserFails(String xml) throws IOException { - assertThat(parseVibrationEffect(xml, /* flags= */ 0)).isNull(); + private void assertPublicApisParserFails(String xml) { + assertThrows("Expected parseVibrationEffect to fail for " + xml, + VibrationXmlParser.ParseFailedException.class, + () -> parseVibrationEffect(xml, /* flags= */ 0)); + assertThrows("Expected parseDocument to fail for " + xml, + VibrationXmlParser.ParseFailedException.class, + () -> parseDocument(xml, /* flags= */ 0)); + } + + private void assertPublicApisParseVibrationEffectFails(String xml) { + assertThrows("Expected parseVibrationEffect to fail for " + xml, + VibrationXmlParser.ParseFailedException.class, + () -> parseVibrationEffect(xml, /* flags= */ 0)); } private void assertPublicApisParserSucceeds(String xml, VibrationEffect effect) - throws IOException { + throws Exception { + assertPublicApisParseDocumentSucceeds(xml, effect); + assertPublicApisParseVibrationEffectSucceeds(xml, effect); + } + + private void assertPublicApisParseDocumentSucceeds(String xml, VibrationEffect... effects) + throws Exception { + assertThat(parseDocument(xml, /* flags= */ 0)) + .isEqualTo(new ParsedVibration(Arrays.asList(effects))); + } + + private void assertPublicApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect) + throws Exception { assertThat(parseVibrationEffect(xml, /* flags= */ 0)).isEqualTo(effect); } + private void assertHiddenApisParserFails(String xml) { + assertThrows("Expected parseVibrationEffect to fail for " + xml, + VibrationXmlParser.ParseFailedException.class, + () -> parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)); + assertThrows("Expected parseDocument to fail for " + xml, + VibrationXmlParser.ParseFailedException.class, + () -> parseDocument(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)); + } + + private void assertHiddenApisParseVibrationEffectFails(String xml) { + assertThrows("Expected parseVibrationEffect to fail for " + xml, + VibrationXmlParser.ParseFailedException.class, + () -> parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)); + } + + private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect) + throws Exception { + assertHiddenApisParseDocumentSucceeds(xml, effect); + assertHiddenApisParseVibrationEffectSucceeds(xml, effect); + } + + private void assertHiddenApisParseDocumentSucceeds(String xml, VibrationEffect... effect) + throws Exception { + assertThat(parseDocument(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)) + .isEqualTo(new ParsedVibration(Arrays.asList(effect))); + } + + private void assertHiddenApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect) + throws Exception { + assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)) + .isEqualTo(effect); + } + + private void assertPublicApisSerializerFails(VibrationEffect effect) { + assertThrows("Expected serialization to fail for " + effect, + VibrationXmlSerializer.SerializationFailedException.class, + () -> serialize(effect)); + } + + private void assertPublicApisSerializerSucceeds(VibrationEffect effect, + String... expectedSegments) throws Exception { + assertSerializationContainsSegments(serialize(effect), expectedSegments); + } + + private void assertHiddenApisSerializerSucceeds(VibrationEffect effect, + String... expectedSegments) throws Exception { + assertSerializationContainsSegments( + serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS), expectedSegments); + } + + private void assertPublicApisRoundTrip(VibrationEffect effect) throws Exception { + assertThat(parseVibrationEffect(serialize(effect, /* flags= */ 0), /* flags= */ 0)) + .isEqualTo(effect); + } + + private void assertHiddenApisRoundTrip(VibrationEffect effect) throws Exception { + String xml = serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS); + assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)) + .isEqualTo(effect); + } + private TypedXmlPullParser createXmlPullParser(String xml) throws Exception { TypedXmlPullParser parser = Xml.newFastPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); @@ -366,11 +523,6 @@ public class VibrationEffectXmlSerializationTest { return parser; } - private void assertParseDocumentSucceeds(String xml, int flags, VibrationEffect... effects) - throws Exception { - assertThat(parseDocument(xml, flags).getVibrationEffects()).containsExactly(effects); - } - /** * Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser * points to the end "vibration" or "vibration-select" tag. @@ -385,7 +537,8 @@ public class VibrationEffectXmlSerializationTest { String tagName = parser.getName(); assertThat(Set.of("vibration-effect", "vibration-select")).contains(tagName); - assertThat(parseElement(parser, flags).getVibrationEffects()).containsExactly(effects); + assertThat(parseElement(parser, flags)) + .isEqualTo(new ParsedVibration(Arrays.asList(effects))); assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG); assertThat(parser.getName()).isEqualTo(tagName); } @@ -405,69 +558,40 @@ public class VibrationEffectXmlSerializationTest { assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT); } - private void assertHiddenApisParseVibrationEffectSucceeds(String xml, VibrationEffect effect) - throws IOException { - assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)) - .isEqualTo(effect); - } - - private void assertPublicApisSerializerFails(VibrationEffect effect) { - assertThrows("Expected serialization to fail for " + effect, - VibrationXmlSerializer.SerializationFailedException.class, - () -> serialize(effect, /* flags= */ 0)); - } - private void assertParseElementFails(String xml) { assertThrows("Expected parsing to fail for " + xml, - VibrationXmlParser.VibrationXmlParserException.class, + VibrationXmlParser.ParseFailedException.class, () -> parseElement(createXmlPullParser(xml), /* flags= */ 0)); } - private void assertPublicApisSerializerSucceeds(VibrationEffect effect, - String... expectedSegments) throws IOException { - assertSerializationContainsSegments(serialize(effect, /* flags= */ 0), expectedSegments); - } - - private void assertHiddenApisSerializerSucceeds(VibrationEffect effect, - String... expectedSegments) throws IOException { - assertSerializationContainsSegments( - serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS), expectedSegments); - } - private void assertSerializationContainsSegments(String xml, String[] expectedSegments) { for (String expectedSegment : expectedSegments) { assertThat(xml).contains(expectedSegment); } } - private void assertPublicApisRoundTrip(VibrationEffect effect) throws IOException { - assertThat(parseVibrationEffect(serialize(effect, /* flags= */ 0), /* flags= */ 0)) - .isEqualTo(effect); - } - - private void assertHiddenApisRoundTrip(VibrationEffect effect) throws IOException { - String xml = serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS); - assertThat(parseVibrationEffect(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)) - .isEqualTo(effect); - } - private static VibrationEffect parseVibrationEffect( - String xml, @VibrationXmlParser.Flags int flags) throws IOException { + String xml, @VibrationXmlParser.Flags int flags) throws Exception { return VibrationXmlParser.parseVibrationEffect(new StringReader(xml), flags); } - private static ParsedVibration parseDocument(String xml, int flags) - throws IOException { + private static ParsedVibration parseDocument(String xml, int flags) throws Exception { return VibrationXmlParser.parseDocument(new StringReader(xml), flags); } private static ParsedVibration parseElement(TypedXmlPullParser parser, int flags) - throws IOException, VibrationXmlParser.VibrationXmlParserException { + throws Exception { return VibrationXmlParser.parseElement(parser, flags); } + private static String serialize(VibrationEffect effect) throws Exception { + StringWriter writer = new StringWriter(); + VibrationXmlSerializer.serialize(effect, writer); + return writer.toString(); + } + private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags) - throws IOException { + throws Exception { StringWriter writer = new StringWriter(); VibrationXmlSerializer.serialize(effect, writer, flags); return writer.toString(); diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml index 7a4b6bc6ad75..99911de09b7d 100644 --- a/data/etc/com.android.settings.xml +++ b/data/etc/com.android.settings.xml @@ -69,5 +69,6 @@ <permission name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" /> <permission name="android.permission.SATELLITE_COMMUNICATION" /> <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" /> + <permission name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" /> </privapp-permissions> </permissions> diff --git a/data/etc/package-shareduid-allowlist.xml b/data/etc/package-shareduid-allowlist.xml index 2401d4a26e68..3b35f446a434 100644 --- a/data/etc/package-shareduid-allowlist.xml +++ b/data/etc/package-shareduid-allowlist.xml @@ -32,4 +32,5 @@ Example usage <config> <allow-package-shareduid package="android.test.settings" shareduid="android.uid.system" /> + <allow-package-shareduid package="com.android.performanceLaunch" shareduid="com.android.performanceapp.tests" /> </config> diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 3256f31bdc93..5caedba364be 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -416,7 +416,8 @@ public class SurfaceTexture { } /** - * Retrieve the dataspace associated with the texture image. + * Retrieve the dataspace associated with the texture image + * set by the most recent call to {@link #updateTexImage}. */ @SuppressLint("MethodNameUnits") public @NamedDataSpace int getDataSpace() { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 290fefa5abfa..822a07c23950 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -169,6 +169,11 @@ class DividerPresenter implements View.OnTouchListener { @GuardedBy("mLock") private int mDividerPosition; + /** Indicates if there are containers to be finished since the divider has appeared. */ + @GuardedBy("mLock") + @VisibleForTesting + private boolean mHasContainersToFinish = false; + DividerPresenter(int taskId, @NonNull DragEventCallback dragEventCallback, @NonNull Executor callbackExecutor) { mTaskId = taskId; @@ -180,7 +185,8 @@ class DividerPresenter implements View.OnTouchListener { void updateDivider( @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentParentInfo parentInfo, - @Nullable SplitContainer topSplitContainer) { + @Nullable SplitContainer topSplitContainer, + boolean isTaskFragmentVanished) { if (!Flags.activityEmbeddingInteractiveDividerFlag()) { return; } @@ -188,6 +194,18 @@ class DividerPresenter implements View.OnTouchListener { synchronized (mLock) { // Clean up the decor surface if top SplitContainer is null. if (topSplitContainer == null) { + // Check if there are containers to finish but the TaskFragment hasn't vanished yet. + // Don't remove the decor surface and divider if so as the removal should happen in + // a following step when the TaskFragment has vanished. This ensures that the decor + // surface is removed only after the resulting Activity is ready to be shown, + // otherwise there may be flicker. + if (mHasContainersToFinish) { + if (isTaskFragmentVanished) { + setHasContainersToFinish(false); + } else { + return; + } + } removeDecorSurfaceAndDivider(wct); return; } @@ -868,6 +886,12 @@ class DividerPresenter implements View.OnTouchListener { } } + void setHasContainersToFinish(boolean hasContainersToFinish) { + synchronized (mLock) { + mHasContainersToFinish = hasContainersToFinish; + } + } + private static boolean isDraggingToFullscreenAllowed( @NonNull DividerAttributes dividerAttributes) { // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index f78e2b5170fc..7ddda1f98809 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -673,7 +673,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen break; case TYPE_TASK_FRAGMENT_VANISHED: mPresenter.removeTaskFragmentInfo(info); - onTaskFragmentVanished(wct, info); + onTaskFragmentVanished(wct, info, taskId); break; case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: onTaskFragmentParentInfoChanged(wct, taskId, @@ -834,7 +834,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, - @NonNull TaskFragmentInfo taskFragmentInfo) { + @NonNull TaskFragmentInfo taskFragmentInfo, int taskId) { final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container != null) { // Cleanup if the TaskFragment vanished is not requested by the organizer. @@ -843,6 +843,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateContainersInTaskIfVisible(wct, container.getTaskId()); } cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); + final TaskContainer taskContainer = getTaskContainer(taskId); + if (taskContainer != null) { + // Update the divider to clean up any decor surfaces. + updateDivider(wct, taskContainer, true /* isTaskFragmentVanished */); + } } /** @@ -884,7 +889,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // The divider need to be updated even if shouldUpdateContainer is false, because the decor // surface may change in TaskFragmentParentInfo, which requires divider update but not // container update. - updateDivider(wct, taskContainer); + updateDivider(wct, taskContainer, false /* isTaskFragmentVanished */); // If the last direct activity of the host task is dismissed and there's an always-on-top // overlay container in the task, the overlay container should also be dismissed. @@ -899,14 +904,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) { final TaskContainer taskContainer = getTaskContainer(taskId); - if (taskContainer != null && taskContainer.isVisible()) { + if (taskContainer == null) { + return; + } + + if (taskContainer.isVisible()) { updateContainersInTask(wct, taskContainer); + } else if (Flags.fixNoContainerUpdateWithoutResize()) { + // the TaskFragmentContainers need to be updated when the task becomes visible + taskContainer.mTaskFragmentContainersNeedsUpdate = true; } } @GuardedBy("mLock") private void updateContainersInTask(@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { + taskContainer.mTaskFragmentContainersNeedsUpdate = false; + // Update all TaskFragments in the Task. Make a copy of the list since some may be // removed on updating. final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); @@ -3257,12 +3271,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @GuardedBy("mLock") - void updateDivider( - @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { + void updateDivider(@NonNull WindowContainerTransaction wct, + @NonNull TaskContainer taskContainer, boolean isTaskFragmentVanished) { final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId()); final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo(); - dividerPresenter.updateDivider( - wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); + final SplitContainer topSplitContainer = taskContainer.getTopNonFinishingSplitContainer(); + if (dividerPresenter != null) { + dividerPresenter.updateDivider( + wct, parentInfo, topSplitContainer, isTaskFragmentVanished); + } } @Override @@ -3292,6 +3309,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final List<TaskFragmentContainer> containersToFinish = new ArrayList<>(); taskContainer.updateTopSplitContainerForDivider( dividerPresenter, containersToFinish); + if (!containersToFinish.isEmpty()) { + dividerPresenter.setHasContainersToFinish(true); + } for (final TaskFragmentContainer container : containersToFinish) { mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index d888fa9d6feb..d0e49d8c403f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -374,7 +374,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes); updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes); - mController.updateDivider(wct, taskContainer); + mController.updateDivider(wct, taskContainer, false /* isTaskFragmentVanished */); } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @@ -757,7 +757,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void expandTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { super.expandTaskFragment(wct, container); - mController.updateDivider(wct, container.getTaskContainer()); + mController.updateDivider( + wct, container.getTaskContainer(), false /* isTaskFragmentVanished */); } static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index ee00c4cd67eb..20ad53ee19a8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -108,6 +108,12 @@ class TaskContainer { private boolean mPlaceholderRuleSuppressed; /** + * {@code true} if the TaskFragments in this Task needs to be updated next time the Task + * becomes visible. See {@link #shouldUpdateContainer(TaskFragmentParentInfo)} + */ + boolean mTaskFragmentContainersNeedsUpdate; + + /** * The {@link TaskContainer} constructor * * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with @@ -185,7 +191,8 @@ class TaskContainer { // If the task properties equals regardless of starting position, don't // need to update the container. - return mInfo.getConfiguration().diffPublicOnly(configuration) != 0 + return mTaskFragmentContainersNeedsUpdate + || mInfo.getConfiguration().diffPublicOnly(configuration) != 0 || mInfo.getDisplayId() != info.getDisplayId(); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index 070fa5bcfae4..859bc2cc40f3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -17,6 +17,7 @@ package androidx.window.extensions.layout; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED; @@ -41,6 +42,7 @@ import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiContext; +import androidx.annotation.VisibleForTesting; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; @@ -138,6 +140,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { throw new IllegalArgumentException("Context must be a UI Context, which should be" + " an Activity, WindowContext or InputMethodService"); } + if (context.getAssociatedDisplayId() == INVALID_DISPLAY) { + Log.w(TAG, "The registered Context is a UI Context but not associated with any" + + " display. This Context may not receive any WindowLayoutInfo update"); + } mFoldingFeatureProducer.getData((features) -> { WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features); consumer.accept(newWindowLayout); @@ -257,7 +263,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } - private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { + @VisibleForTesting + void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { synchronized (mLock) { mLastReportedFoldingFeatures.clear(); mLastReportedFoldingFeatures.addAll(storedFeatures); @@ -409,9 +416,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * @return true if the display features should be reported for the UI Context, false otherwise. */ private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) { - int displayId = context.getDisplay().getDisplayId(); + int displayId = context.getAssociatedDisplayId(); if (displayId != DEFAULT_DISPLAY) { - // Display features are not supported on secondary displays. + // Display features are not supported on secondary displays or the context is not + // associated with any display. return false; } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index 4f51815ed05d..bc18cd289e05 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -82,6 +82,7 @@ import java.util.concurrent.Executor; */ @Presubmit @SmallTest +@SuppressWarnings("GuardedBy") @RunWith(AndroidJUnit4.class) public class DividerPresenterTest { @Rule @@ -186,7 +187,8 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - mSplitContainer); + mSplitContainer, + false /* isTaskFragmentVanished */); assertNotEquals(mProperties, mDividerPresenter.mProperties); verify(mRenderer).update(); @@ -206,7 +208,8 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - mSplitContainer); + mSplitContainer, + false /* isTaskFragmentVanished */); assertNotEquals(mProperties, mDividerPresenter.mProperties); verify(mRenderer).update(); @@ -222,7 +225,8 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - mSplitContainer); + mSplitContainer, + false /* isTaskFragmentVanished */); assertEquals(mProperties, mDividerPresenter.mProperties); verify(mRenderer, never()).update(); @@ -234,7 +238,42 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - null /* splitContainer */); + null /* splitContainer */, + false /* isTaskFragmentVanished */); + final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder( + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) + .build(); + + verify(mTransaction).addTaskFragmentOperation( + mPrimaryContainerToken, taskFragmentOperation); + verify(mRenderer).release(); + assertNull(mDividerPresenter.mRenderer); + assertNull(mDividerPresenter.mProperties); + assertNull(mDividerPresenter.mDecorSurfaceOwner); + } + + @Test + public void testUpdateDivider_noChangeWhenHasContainersToFinishButTaskFragmentNotVanished() { + mDividerPresenter.setHasContainersToFinish(true); + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + null /* splitContainer */, + false /* isTaskFragmentVanished */); + + assertEquals(mProperties, mDividerPresenter.mProperties); + verify(mRenderer, never()).update(); + verify(mTransaction, never()).addTaskFragmentOperation(any(), any()); + } + + @Test + public void testUpdateDivider_dividerRemovedWhenHasContainersToFinishAndTaskFragmentVanished() { + mDividerPresenter.setHasContainersToFinish(true); + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + null /* splitContainer */, + true /* isTaskFragmentVanished */); final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder( OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) .build(); @@ -254,7 +293,8 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - mSplitContainer); + mSplitContainer, + false /* isTaskFragmentVanished */); final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder( OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) .build(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 640b1fced455..efeec82b782e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -200,12 +200,14 @@ public class SplitControllerTest { public void testOnTaskFragmentVanished() { final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken(); + doReturn(createTestTaskContainer()).when(mSplitController).getTaskContainer(TASK_ID); // The TaskFragment has been removed in the server, we only need to cleanup the reference. - mSplitController.onTaskFragmentVanished(mTransaction, mInfo); + mSplitController.onTaskFragmentVanished(mTransaction, mInfo, TASK_ID); verify(mSplitPresenter, never()).deleteTaskFragment(any(), any()); verify(mSplitController).removeContainer(tf); + verify(mSplitController).updateDivider(any(), any(), anyBoolean()); verify(mTransaction, never()).finishActivity(any()); } @@ -1152,7 +1154,7 @@ public class SplitControllerTest { .setTaskFragmentInfo(info)); mSplitController.onTransactionReady(transaction); - verify(mSplitController).onTaskFragmentVanished(any(), eq(info)); + verify(mSplitController).onTaskFragmentVanished(any(), eq(info), anyInt()); verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), anyInt(), anyBoolean()); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java new file mode 100644 index 000000000000..ff0a82fe05d6 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.layout; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.content.ContextWrapper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; + +/** + * Test class for {@link WindowLayoutComponentImpl}. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:WindowLayoutComponentImplTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class WindowLayoutComponentImplTest { + + private WindowLayoutComponentImpl mWindowLayoutComponent; + + @Before + public void setUp() { + mWindowLayoutComponent = new WindowLayoutComponentImpl( + ApplicationProvider.getApplicationContext(), + mock(DeviceStateManagerFoldingFeatureProducer.class)); + } + + @Test + public void testAddWindowLayoutListenerOnFakeUiContext_noCrash() { + final Context fakeUiContext = createTestContext(); + + mWindowLayoutComponent.addWindowLayoutInfoListener(fakeUiContext, info -> {}); + + mWindowLayoutComponent.onDisplayFeaturesChanged(Collections.emptyList()); + } + + private static Context createTestContext() { + return new FakeUiContext(ApplicationProvider.getApplicationContext()); + } + + /** + * A {@link android.content.Context} overrides {@link android.content.Context#isUiContext} to + * {@code true}. + */ + private static class FakeUiContext extends ContextWrapper { + + FakeUiContext(Context base) { + super(base); + } + + @Override + public boolean isUiContext() { + return true; + } + } +} diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index bcb1d292fce2..52ae93f5ebf1 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -29,6 +29,7 @@ android:name=".desktopmode.DesktopWallpaperActivity" android:excludeFromRecents="true" android:launchMode="singleInstance" + android:showForAllUsers="true" android:theme="@style/DesktopWallpaperTheme" /> <activity diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 112eb617e7a6..3b7eb292abc7 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -121,3 +121,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_taskbar_on_phones" + namespace: "multitasking" + description: "Enables taskbar on phones" + bug: "348007377" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml index 34f03c2f226b..501bedd50f55 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml @@ -19,7 +19,7 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" - android:id="@+id/bubble_bar_expanded_view"> + android:id="@+id/bubble_expanded_view"> <com.android.wm.shell.bubbles.bar.BubbleBarHandleView android:id="@+id/bubble_bar_handle_view" diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 1c8f5e60c5c9..8b328e2c79cf 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Borrel"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Bestuur"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Borrel is toegemaak."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tik om hierdie app te herbegin vir ’n beter aansig"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Verander hierdie app se aspekverhouding in Instellings"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Verander aspekverhouding"</string> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 81ab3ab15aad..b005a01711eb 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"አረፋ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ያቀናብሩ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"አረፋ ተሰናብቷል።"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"ለተሻለ ዕይታ ይህን መተግበሪያ እንደገና ለመጀመር መታ ያድርጉ"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"የዚህን መተግበሪያ ምጥጥነ ገፅታ በቅንብሮች ውስጥ ይለውጡ"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"የምጥጥነ ገፅታ ለውጥ"</string> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 3974c39d4803..8c283d33eff6 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"فقاعة"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"إدارة"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"تم إغلاق الفقاعة."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"انقر لإعادة تشغيل هذا التطبيق للحصول على تجربة عرض أفضل."</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"يمكنك تغيير نسبة العرض إلى الارتفاع لهذا التطبيق من خلال \"الإعدادات\"."</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغيير نسبة العرض إلى الارتفاع"</string> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index a1ce1b3b9513..ef92587ad274 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"পৰিচালনা কৰক"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল অগ্ৰাহ্য কৰা হৈছে"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"উন্নত ভিউ পোৱাৰ বাবে এপ্টো ৰিষ্টাৰ্ট কৰিবলৈ টিপক"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ছেটিঙলৈ গৈ এই এপ্টোৰ আকাৰৰ অনুপাত সলনি কৰক"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"আকাৰৰ অনুপাত সলনি কৰক"</string> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 71dfe5ac6bed..04b2f1c23bc4 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Qabarcıq"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"İdarə edin"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Qabarcıqdan imtina edilib."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Yaxşı görünüş üçün toxunaraq bu tətbiqi yenidən başladın"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ayarlarda bu tətbiqin tərəflər nisbətini dəyişin"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tərəflər nisbətini dəyişin"</string> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml index f48360991d49..47bc105f19bc 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljajte"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promenite razmeru ove aplikacije u Podešavanjima"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promeni razmeru"</string> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 532ecc64358f..6ad7553a8383 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Усплывальнае апавяшчэнне"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Кіраваць"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Усплывальнае апавяшчэнне адхілена."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Націсніце, каб перазапусціць гэту праграму для зручнейшага прагляду"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Змяніць суадносіны бакоў для гэтай праграмы ў наладах"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Змяніць суадносіны бакоў"</string> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 8f828badcf47..a9e0bce81376 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управление"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отхвърлено."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Докоснете, за да рестартирате това приложение с цел по-добър изглед"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Променете съотношението на това приложение в „Настройки“"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Промяна на съотношението"</string> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index e0a2ea824be0..29de1007e311 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ম্যানেজ করুন"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল বাতিল করা হয়েছে।"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"আরও ভাল ভিউয়ের জন্য এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"সেটিংস থেকে এই অ্যাপের অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index 41c72c1d3a03..5f1da7571d95 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljaj"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijenite format slike aplikacije u Postavkama"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijenite format slike"</string> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 679227248ea5..d70de794ba17 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bombolla"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestiona"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"La bombolla s\'ha ignorat."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Canvia la relació d\'aspecte d\'aquesta aplicació a Configuració"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Canvia la relació d\'aspecte"</string> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index 150a6e642529..ca00fec12e86 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovat"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina byla zavřena."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Klepnutím tuto aplikaci restartujete kvůli lepšímu zobrazení"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Změnit v Nastavení poměr stran této aplikace"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Změnit poměr stran"</string> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index 8878910a4d2c..d50d2f0f135d 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen blev lukket."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tryk for at genstarte denne app, så visningen forbedres"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Skift denne apps billedformat i Indstillinger"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Skift billedformat"</string> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 7b91559e9179..7f44f83a1790 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Verwalten"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble verworfen."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tippen, um diese App neu zu starten und die Ansicht zu verbessern"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Seitenverhältnis der App in den Einstellungen ändern"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Seitenverhältnis ändern"</string> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 14e5e2f87ab8..a3a5ccd839e4 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Συννεφάκι"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Διαχείριση"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Το συννεφάκι παραβλέφθηκε."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Πατήστε για να επανεκκινήσετε αυτή την εφαρμογή για καλύτερη προβολή"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Αλλάξτε τον λόγο διαστάσεων αυτής της εφαρμογής στις Ρυθμίσεις"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Αλλαγή λόγου διαστάσεων"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index 7427b62679be..edc4f4e25c2a 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index cb9ee4f6b6b3..e537f0a80144 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index 7427b62679be..edc4f4e25c2a 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index 7427b62679be..edc4f4e25c2a 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml index 8498807f9fdb..bdcd275d9c14 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index 406c1f37c455..8653e5932a04 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Cuadro"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Administrar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Se descartó el cuadro."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Presiona para reiniciar esta app y tener una mejor vista"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta app en Configuración"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</string> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 0583d79da127..8f59c9c91d20 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Burbuja"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbuja cerrada."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Toca para reiniciar esta aplicación y obtener una mejor vista"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta aplicación en Ajustes"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</string> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index 70547f566ea6..3d86eb4a91d9 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Mull"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Halda"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Mullist loobuti."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muutke selle rakenduse kuvasuhet jaotises Seaded"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Muutke kuvasuhet"</string> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index 4be35eac6c1f..4e7bdd246d10 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Burbuila"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Kudeatu"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Baztertu da globoa."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Hobeto ikusteko, sakatu hau, eta aplikazioa berrabiarazi egingo da"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Aldatu aplikazioaren aspektu-erlazioa ezarpenetan"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Aldatu aspektu-erlazioa"</string> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 32d5f5f34fb8..39100425a9ac 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"حباب"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"مدیریت"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"حبابک رد شد."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراهاندازی شود"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"نسبت ابعادی این برنامه را در «تنظیمات» تغییر دهید"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغییر نسبت ابعادی"</string> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index 6f03545e5542..577d625ba8f8 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Kupla"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Ylläpidä"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Kupla ohitettu."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muuta tämän sovelluksen kuvasuhdetta Asetuksissa"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Vaihda kuvasuhdetta"</string> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 3492f136c4f9..74d822ac02c0 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle ignorée."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette application dans les paramètres"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier les proportions"</string> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index 4002e4d04d51..4d14d0b85f3e 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle fermée."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Appuyez pour redémarrer cette appli et obtenir une meilleure vue."</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Modifiez le format de cette appli dans les Paramètres."</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier le format"</string> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index c371f7f62feb..e5b67c2aaad1 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Burbulla"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Xestionar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ignorouse a burbulla."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia a proporción desta aplicación en Configuración"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar a proporción"</string> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 7e3d7a373be4..e2a52dccd8ea 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"બબલ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"મેનેજ કરો"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"બબલ છોડી દેવાયો."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"વધુ સારા વ્યૂ માટે, આ ઍપને ફરી શરૂ કરવા ટૅપ કરો"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"સેટિંગમાં આ ઍપનો સાપેક્ષ ગુણોત્તર બદલો"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"સાપેક્ષ ગુણોત્તર બદલો"</string> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index cd0f4e3618f7..f75e0e0528e1 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"बेहतर व्यू पाने के लिए, टैप करके ऐप्लिकेशन को रीस्टार्ट करें"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिंग में जाकर इस ऐप्लिकेशन का आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 27d4cfcf22d5..ed80c505d756 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić odbačen."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijeni omjer slike ove aplikacije u postavkama"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijeni omjer slike"</string> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index a8cc5c120efc..32a31063bd90 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Buborék"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Kezelés"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Buborék elvetve."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Az app méretarányát a Beállításokban módosíthatja"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Méretarány módosítása"</string> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 7f372774241a..65ca704ded09 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Պղպջակ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Կառավարել"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ամպիկը փակվեց։"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Փոխել հավելվածի կողմերի հարաբերակցությունը Կարգավորումներում"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Փոխել չափերի հարաբերակցությունը"</string> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 3cf55fa0ede2..975dd72f67d7 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Kelola"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon ditutup."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ubah rasio aspek aplikasi ini di Setelan"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ubah rasio aspek"</string> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index 6aa56f9858ad..11c47189dce0 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Blaðra"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Stjórna"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Blöðru lokað."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Ýttu til að endurræsa forritið og fá betri sýn"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Breyta myndhlutfalli þessa forrits í stillingunum"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Breyta myndhlutfalli"</string> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index 3c1d5e4dac02..168c8cc5936f 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Fumetto"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Fumetto ignorato."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tocca per riavviare l\'app e migliorare la visualizzazione"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia le proporzioni dell\'app nelle Impostazioni"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambia proporzioni"</string> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index a0c3b3a95ca8..fd4cd1adaa2a 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"בועה"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ניהול"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"הבועה נסגרה."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"אפשר לשנות את יחס הגובה-רוחב של האפליקציה הזו ב\'הגדרות\'"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"שינוי יחס גובה-רוחב"</string> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index fb726c180997..64ddec9450ae 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"バブル"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ふきだしが非表示になっています。"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"タップしてこのアプリを再起動すると、表示が適切になります"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"このアプリのアスペクト比を [設定] で変更します"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"アスペクト比を変更"</string> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index e9f620a17203..cab8807b86d1 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ბუშტი"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"მართვა"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ბუშტი დაიხურა."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"შეხებით გადატვირთეთ ეს აპი უკეთესი ხედის მისაღებად"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"შეცვალეთ ამ აპის თანაფარდობა პარამეტრებიდან"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"თანაფარდობის შეცვლა"</string> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index 34e41038f285..4ff5b85b36fd 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Көпіршік"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Басқару"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқыма хабар жабылды."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Көріністі жақсарту үшін осы қолданбаны түртіп, қайта ашыңыз."</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Осы қолданбаның арақатынасын параметрлерден өзгертуге болады."</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Арақатынасты өзгерту"</string> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 362bbad4ec12..ba7a32495659 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ពពុះ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"គ្រប់គ្រង"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"បានច្រានចោលសារលេចឡើង។"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"ចុចដើម្បីចាប់ផ្ដើមកម្មវិធីនេះឡើងវិញសម្រាប់ទិដ្ឋភាពកាន់តែប្រសើរ"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ផ្លាស់ប្ដូរសមាមាត្ររបស់កម្មវិធីនេះនៅក្នុងការកំណត់"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ប្ដូរសមាមាត្រ"</string> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index 77cc4a44f81a..423e8d53a654 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ಬಬಲ್"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ನಿರ್ವಹಿಸಿ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ಬಬಲ್ ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಈ ಆ್ಯಪ್ನ ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index caa114fd6f88..0d1c6216776b 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"버블"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"관리"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"대화창을 닫았습니다."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"탭하면 앱을 다시 시작하여 보기를 개선합니다."</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"설정에서 앱의 가로세로 비율을 변경합니다."</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"가로세로 비율 변경"</string> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 302c0071a73a..f17e9ca891c0 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Көбүк"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Башкаруу"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Калкып чыкма билдирме жабылды."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Жакшыраак көрүү үчүн бул колдонмону өчүрүп күйгүзүңүз. Ал үчүн таптап коюңуз"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Бул колдонмонун тараптарынын катнашын параметрлерден өзгөртүү"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Тараптардын катнашын өзгөртүү"</string> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index a3519636b71f..195e4d56a1c1 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ຟອງ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ຈັດການ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ປິດ Bubble ໄສ້ແລ້ວ."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ເພື່ອມຸມມອງທີ່ດີຂຶ້ນ"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ປ່ຽນອັດຕາສ່ວນຂອງແອັບນີ້ໃນການຕັ້ງຄ່າ"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ປ່ຽນອັດຕາສ່ວນ"</string> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index e4dd7398f679..63ad580a81cc 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Debesėlis"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Tvarkyti"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Debesėlio atsisakyta."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Pakeiskite šios programos kraštinių santykį"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Keisti kraštinių santykį"</string> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index 99aebf626322..268d89324f54 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Burbulis"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Pārvaldīt"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbulis ir noraidīts."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Iestatījumos mainiet šīs lietotnes malu attiecību."</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mainīt malu attiecību"</string> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index c152c60fa631..0a0027fa1bae 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управувајте"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отфрлено."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Допрете за да ја рестартирате апликацијава за подобар приказ"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Промени го соодносот на апликацијава во „Поставки“"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Променување на соодносот"</string> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index 90275cdb517a..07809e1a0014 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ബബിൾ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"മാനേജ് ചെയ്യുക"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ബബിൾ ഡിസ്മിസ് ചെയ്തു."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"മികച്ച കാഴ്ചയ്ക്കായി ഈ ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ഈ ആപ്പിന്റെ വീക്ഷണ അനുപാതം, ക്രമീകരണത്തിൽ മാറ്റുക"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"വീക്ഷണ അനുപാതം മാറ്റുക"</string> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 5e43506ab621..99bd2dffca53 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Бөмбөлөг"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Удирдах"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Бөмбөлгийг үл хэрэгссэн."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Харагдах байдлыг сайжруулахын тулд энэ аппыг товшиж, дахин эхлүүлнэ үү"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Энэ аппын харьцааг Тохиргоонд өөрчилнө үү"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Харьцааг өөрчлөх"</string> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 5874bffc9199..ac57e0a549b4 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापित करा"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल डिसमिस केला."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"अधिक चांगल्या दृश्यासाठी हे अॅप रीस्टार्ट करण्याकरिता टॅप करा"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिंग्ज मध्ये या ॲपचा आस्पेक्ट रेशो बदला"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"आस्पेक्ट रेशो बदला"</string> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 4de8a7b03547..6bc2fbb27c51 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Gelembung"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Urus"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Gelembung diketepikan."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Tukar nisbah bidang apl ini dalam Tetapan"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tukar nisbah bidang"</string> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 5b9e9cb7353e..12c19edcaeb1 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ပူဖောင်းဖောက်သံ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"စီမံရန်"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ပူဖောင်းကွက် ဖယ်လိုက်သည်။"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"ပိုကောင်းသောမြင်ကွင်းအတွက် ဤအက်ပ်ပြန်စရန် တို့ပါ"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ဆက်တင်များတွင် ဤအက်ပ်၏အချိုးအစားကို ပြောင်းရန်"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"အချိုးစား ပြောင်းရန်"</string> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 6005be4fb5c9..1161eb608405 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen er avvist."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Trykk for å starte denne appen på nytt og få en bedre visning"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Endre høyde/bredde-forholdet for denne appen i Innstillinger"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Endre høyde/bredde-forholdet"</string> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index a5bd2ab5c10b..25d033738c86 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापन गर्नुहोस्"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल हटाइयो।"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"अझ राम्रो भ्यू प्राप्त गर्नका लागि यो एप रिस्टार्ट गर्न ट्याप गर्नुहोस्"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिङमा गई यो एपको एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 0cd27c5c1457..4ad343cb1a4e 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubbel gesloten."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tik om deze app opnieuw op te starten voor een betere weergave"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Wijzig de beeldverhouding van deze app in Instellingen"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Beeldverhouding wijzigen"</string> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index bf751852a255..966d40440ac7 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ବବଲ୍"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ପରିଚାଳନା କରନ୍ତୁ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ବବଲ୍ ଖାରଜ କରାଯାଇଛି।"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"ଏକ ଆହୁରି ଭଲ ଭ୍ୟୁ ପାଇଁ ଏହି ଆପ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ସେଟିଂସରେ ଏହି ଆପର ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index 325c1e80c433..9feaf41cda7f 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ਬੁਲਬੁਲਾ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕੀਤਾ ਗਿਆ।"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਵਾਸਤੇ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਇਸ ਐਪ ਦੇ ਆਕਾਰ ਅਨੁਪਾਤ ਨੂੰ ਬਦਲੋ"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ"</string> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index a7648c8e323b..1c7fbf8e80d3 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Dymek"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Zarządzaj"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Zamknięto dymek"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Kliknij w celu zrestartowania aplikacji, aby lepiej się wyświetlała."</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmień proporcje obrazu aplikacji w Ustawieniach"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmień proporcje obrazu"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index e47d151337b2..5c2de2ad0d31 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude o tamanho da janela deste app nas Configurações"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporção"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index 1210fe8fda05..6f76525473eb 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Balão"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gerir"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão ignorado."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar esta app e ficar com uma melhor visão"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Altere o formato desta app nas Definições"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Altere o formato"</string> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index e47d151337b2..5c2de2ad0d31 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude o tamanho da janela deste app nas Configurações"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporção"</string> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index ae871f3dd42b..6e85e7849d95 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionează"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Atinge ca să repornești aplicația pentru o vizualizare mai bună"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Schimbă raportul de dimensiuni al aplicației din Setări"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Schimbă raportul de dimensiuni"</string> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index 971e146ba77e..1b41983cd1a3 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Всплывающая подсказка"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Настроить"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Всплывающий чат закрыт."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Нажмите, чтобы перезапустить приложение и оптимизировать размер"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Изменить соотношение сторон приложения в настройках"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Изменить соотношение сторон"</string> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index ef1381cbe635..6fd37e91c8b0 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"බුබුළු"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"කළමනා කරන්න"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"බුබුල ඉවත දමා ඇත."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"වඩා හොඳ දසුනක් සඳහා මෙම යෙදුම යළි ඇරඹීමට තට්ටු කරන්න"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"සැකසීම් තුළ මෙම යෙදුමේ දර්ශන අනුපාතය වෙනස් කරන්න"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"දර්ශන අනුපාතය වෙනස් කරන්න"</string> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index 55a03122483b..dabbf397d38f 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovať"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina bola zavretá."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmeniť pomer strán tejto aplikácie v Nastaveniach"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmeniť pomer strán"</string> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index bb123dcdbfb6..3ade33810cc8 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Mehurček"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblaček je bil opuščen."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Razmerje stranic te aplikacije spremenite v nastavitvah."</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Sprememba razmerja stranic"</string> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index c74a8cd23338..ee1aa00c5cbe 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Flluskë"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Menaxho"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Flluska u hoq."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Trokit për ta rinisur këtë aplikacion për një pamje më të mirë"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ndrysho raportin e pamjes së këtij aplikacioni te \"Cilësimet\""</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ndrysho raportin e pamjes"</string> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 0694a973dc1e..b2868ca84dac 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Облачић"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управљајте"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Облачић је одбачен."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Додирните да бисте рестартовали ову апликацију ради бољег приказа"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Промените размеру ове апликације у Подешавањима"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Промени размеру"</string> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 8e0bcfe91679..66118efd5da7 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubbla"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Hantera"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubblan ignorerades."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Tryck för att starta om appen och få en bättre vy"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ändra appens bildformat i inställningarna"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ändra bildformat"</string> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index 41180abcf712..863b49b1f010 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Kiputo"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Dhibiti"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Umeondoa kiputo."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Badilisha uwiano wa programu hii katika Mipangilio"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Badilisha uwiano wa kipengele"</string> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index 01ac78d984f3..74e0207bf62e 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"பபிள்"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"நிர்வகி"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"குமிழ் நிராகரிக்கப்பட்டது."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"அமைப்புகளில் இந்த ஆப்ஸின் தோற்ற விகிதத்தை மாற்றும்"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"தோற்ற விகிதத்தை மாற்றும்"</string> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 6224e72c19fe..35711567e760 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"బబుల్"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"మేనేజ్ చేయండి"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"బబుల్ విస్మరించబడింది."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"మెరుగైన వీక్షణ కోసం ఈ యాప్ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేయండి"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"సెట్టింగ్లలో ఈ యాప్ ఆకార నిష్పత్తిని మార్చండి"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ఆకార నిష్పత్తిని మార్చండి"</string> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index fe0b74c469f4..47694164270e 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"บับเบิล"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"จัดการ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ปิดบับเบิลแล้ว"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"แตะเพื่อรีสตาร์ทแอปนี้และรับมุมมองที่ดียิ่งขึ้น"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"เปลี่ยนสัดส่วนภาพของแอปนี้ในการตั้งค่า"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"เปลี่ยนอัตราส่วนกว้างยาว"</string> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index 786e99cfe8c8..be18d88194c1 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Pamahalaan"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Na-dismiss na ang bubble."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"I-tap para i-restart ang app na ito para sa mas magandang view"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Baguhin ang aspect ratio ng app na ito sa Mga Setting"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Baguhin ang aspect ratio"</string> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index e953f5808aff..4c8c53610711 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Baloncuk"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Yönet"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon kapatıldı."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Bu uygulamanın en boy oranını Ayarlar\'dan değiştirin"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"En boy oranını değiştir"</string> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index fbdf42e582d1..7cc1a0406f97 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Спливаюче сповіщення"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Налаштувати"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Спливаюче сповіщення закрито."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Натисніть, щоб перезапустити цей додаток для зручнішого перегляду"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Змінити формат для цього додатка в налаштуваннях"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Змінити формат"</string> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index 5562fa70bf09..8b9f29969d80 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"بلبلہ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"نظم کریں"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"بلبلہ برخاست کر دیا گیا۔"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"بہتر منظر کے لیے اس ایپ کو ری اسٹارٹ کرنے کی خاطر تھپتھپائیں"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ترتیبات میں اس ایپ کی تناسبی شرح کو تبدیل کریں"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تناسبی شرح کو تبدیل کریں"</string> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 50e42329a1a0..55c6b32c909b 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Pufaklar"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Boshqarish"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulutcha yopildi."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Sozlamalar orqali bu ilovaning tomonlar nisbatini oʻzgartiring"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tomonlar nisbatini oʻzgartirish"</string> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 6da85881210d..07a6b6f6c2b4 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bong bóng"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Quản lý"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Đã đóng bong bóng."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Nhấn nút khởi động lại ứng dụng này để xem dễ hơn"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Thay đổi tỷ lệ khung hình của ứng dụng này thông qua phần Cài đặt"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Thay đổi tỷ lệ khung hình"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index 4318caf26199..908095a163a7 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"气泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭消息气泡。"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"点按即可重启此应用,获得更好的视觉体验"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"在“设置”中更改此应用的宽高比"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"更改高宽比"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index 72cd39d8e00a..c8550b4e0611 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"氣泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"對話氣泡已關閉。"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"輕按並重新啟動此應用程式,以取得更佳的觀看體驗"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"前往「設定」變更此應用程式的長寬比"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"變更長寬比"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index c06d7b105694..67048335de64 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"泡泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已關閉泡泡。"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"輕觸此按鈕重新啟動這個應用程式,即可獲得更良好的觀看體驗"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"前往「設定」變更這個應用程式的顯示比例"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"變更顯示比例"</string> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 755414e52762..96b4faec06b8 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Ibhamuza"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Phatha"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ibhamuza licashisiwe."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Shintsha ukubukeka kwesilinganiselo kwe-app kuMasethingi"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Shintsha ukubukeka kwesilinganiselo"</string> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index a426b206b0cd..5a42817e839b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -31,6 +31,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_ import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; @@ -45,6 +46,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.shared.TransitionUtil; @@ -398,7 +400,15 @@ class ActivityEmbeddingAnimationRunner { // This is because the TaskFragment surface/change won't contain the Activity's before its // reparent. Animation changeAnimation = null; - Rect parentBounds = new Rect(); + final Rect parentBounds = new Rect(); + // We use a single boolean value to record the backdrop override because the override used + // for overlay and we restrict to single overlay animation. We should fix the assumption + // if we allow multiple overlay transitions. + // The backdrop logic is mainly for animations of split animations. The backdrop should be + // disabled if there is any open/close target in the same transition as the change target. + // However, the overlay change animation usually contains one change target, and shows + // backdrop unexpectedly. + Boolean overrideShowBackdrop = null; for (TransitionInfo.Change change : info.getChanges()) { if (change.getMode() != TRANSIT_CHANGE || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { @@ -421,17 +431,17 @@ class ActivityEmbeddingAnimationRunner { } } - // The TaskFragment may be enter/exit split, so we take the union of both as the parent - // size. - parentBounds.union(boundsAnimationChange.getStartAbsBounds()); - parentBounds.union(boundsAnimationChange.getEndAbsBounds()); - if (boundsAnimationChange != change) { - // Union the change starting bounds in case the activity is resized and reparented - // to a TaskFragment. In that case, the TaskFragment may not cover the activity's - // starting bounds. - parentBounds.union(change.getStartAbsBounds()); + final TransitionInfo.AnimationOptions options = boundsAnimationChange + .getAnimationOptions(); + if (options != null) { + final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions( + options, TRANSIT_CHANGE); + if (overrideAnimation != null) { + overrideShowBackdrop = overrideAnimation.getShowBackdrop(); + } } + calculateParentBounds(change, boundsAnimationChange, parentBounds); // There are two animations in the array. The first one is for the start leash // (snapshot), and the second one is for the end leash (TaskFragment). final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, @@ -466,7 +476,7 @@ class ActivityEmbeddingAnimationRunner { // If there is no corresponding open/close window with the change, we should show background // color to cover the empty part of the screen. - boolean shouldShouldBackgroundColor = true; + boolean shouldShowBackgroundColor = true; // Handle the other windows that don't have bounds change in the same transition. for (TransitionInfo.Change change : info.getChanges()) { if (handledChanges.contains(change)) { @@ -483,16 +493,18 @@ class ActivityEmbeddingAnimationRunner { animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); } else if (TransitionUtil.isClosingType(change.getMode())) { animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); - shouldShouldBackgroundColor = false; + shouldShowBackgroundColor = false; } else { animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds); - shouldShouldBackgroundColor = false; + shouldShowBackgroundColor = false; } adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, TransitionUtil.getRootFor(change, info))); } - if (shouldShouldBackgroundColor && changeAnimation != null) { + shouldShowBackgroundColor = overrideShowBackdrop != null + ? overrideShowBackdrop : shouldShowBackgroundColor; + if (shouldShowBackgroundColor && changeAnimation != null) { // Change animation may leave part of the screen empty. Show background color to cover // that. changeAnimation.setShowBackdrop(true); @@ -502,6 +514,39 @@ class ActivityEmbeddingAnimationRunner { } /** + * Calculates parent bounds of the animation target by {@code change}. + */ + @VisibleForTesting + static void calculateParentBounds(@NonNull TransitionInfo.Change change, + @NonNull TransitionInfo.Change boundsAnimationChange, @NonNull Rect outParentBounds) { + if (Flags.activityEmbeddingOverlayPresentationFlag()) { + final Point endParentSize = change.getEndParentSize(); + if (endParentSize.equals(0, 0)) { + return; + } + final Point endRelPosition = change.getEndRelOffset(); + final Point endAbsPosition = new Point(change.getEndAbsBounds().left, + change.getEndAbsBounds().top); + final Point parentEndAbsPosition = new Point(endAbsPosition.x - endRelPosition.x, + endAbsPosition.y - endRelPosition.y); + outParentBounds.set(parentEndAbsPosition.x, parentEndAbsPosition.y, + parentEndAbsPosition.x + endParentSize.x, + parentEndAbsPosition.y + endParentSize.y); + } else { + // The TaskFragment may be enter/exit split, so we take the union of both as + // the parent size. + outParentBounds.union(boundsAnimationChange.getStartAbsBounds()); + outParentBounds.union(boundsAnimationChange.getEndAbsBounds()); + if (boundsAnimationChange != change) { + // Union the change starting bounds in case the activity is resized and + // reparented to a TaskFragment. In that case, the TaskFragment may not cover + // the activity's starting bounds. + outParentBounds.union(change.getStartAbsBounds()); + } + } + } + + /** * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one. * The screenshot leash should be attached to the {@code animationChange} surface which we will * animate later. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index b9868629e64b..8d49614b021b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -18,6 +18,8 @@ package com.android.wm.shell.activityembedding; import static android.app.ActivityOptions.ANIM_CUSTOM; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.window.TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; @@ -27,6 +29,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; +import android.util.Log; +import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; @@ -203,7 +207,7 @@ class ActivityEmbeddingAnimationSpec { Animation loadOpenAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); - final Animation customAnimation = loadCustomAnimation(info, change, isEnter); + final Animation customAnimation = loadCustomAnimation(info, change); final Animation animation; if (customAnimation != null) { animation = customAnimation; @@ -230,7 +234,7 @@ class ActivityEmbeddingAnimationSpec { Animation loadCloseAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); - final Animation customAnimation = loadCustomAnimation(info, change, isEnter); + final Animation customAnimation = loadCustomAnimation(info, change); final Animation animation; if (customAnimation != null) { animation = customAnimation; @@ -263,18 +267,40 @@ class ActivityEmbeddingAnimationSpec { @Nullable private Animation loadCustomAnimation(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, boolean isEnter) { + @NonNull TransitionInfo.Change change) { final TransitionInfo.AnimationOptions options; if (Flags.moveAnimationOptionsToChange()) { options = change.getAnimationOptions(); } else { options = info.getAnimationOptions(); } + return loadCustomAnimationFromOptions(options, change.getMode()); + } + + @Nullable + Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options, + @WindowManager.TransitionType int mode) { if (options == null || options.getType() != ANIM_CUSTOM) { return null; } + final int resId; + if (TransitionUtil.isOpeningType(mode)) { + resId = options.getEnterResId(); + } else if (TransitionUtil.isClosingType(mode)) { + resId = options.getExitResId(); + } else if (mode == TRANSIT_CHANGE) { + resId = options.getChangeResId(); + } else { + Log.w(TAG, "Unknown transit type:" + mode); + resId = DEFAULT_ANIMATION_RESOURCES_ID; + } + // Use the default animation if the resources ID is not specified. + if (resId == DEFAULT_ANIMATION_RESOURCES_ID) { + return null; + } + final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), - isEnter ? options.getEnterResId() : options.getExitResId()); + resId); if (anim != null) { return anim; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 1279fc42c066..2aefc64a3ebb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -894,11 +894,22 @@ public class Bubble implements BubbleViewProvider { } @Nullable - Intent getAppBubbleIntent() { + @VisibleForTesting + public Intent getAppBubbleIntent() { return mAppIntent; } /** + * Sets the intent for a bubble that is an app bubble (one for which {@link #mIsAppBubble} is + * true). + * + * @param appIntent The intent to set for the app bubble. + */ + void setAppBubbleIntent(Intent appIntent) { + mAppIntent = appIntent; + } + + /** * Returns whether this bubble is from an app versus a notification. */ public boolean isAppBubble() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index d2c36e6b637c..c853301519e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1450,6 +1450,8 @@ public class BubbleController implements ConfigurationChangeListener, if (b != null) { // It's in the overflow, so remove it & reinflate mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL); + // Update the bubble entry in the overflow with the latest intent. + b.setAppBubbleIntent(intent); } else { // App bubble does not exist, lets add and expand it b = Bubble.createAppBubble(intent, user, icon, mMainExecutor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java index 6ffeb97f50fa..58007b50350b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java @@ -27,7 +27,9 @@ import android.util.DisplayMetrics; import android.util.Size; import android.view.Gravity; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -39,6 +41,9 @@ public class PipBoundsAlgorithm { private static final String TAG = PipBoundsAlgorithm.class.getSimpleName(); private static final float INVALID_SNAP_FRACTION = -1f; + // The same value (with the same name) is used in Launcher. + private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f; + @NonNull private final PipBoundsState mPipBoundsState; @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState; @NonNull protected final SizeSpecSource mSizeSpecSource; @@ -206,9 +211,27 @@ public class PipBoundsAlgorithm { */ public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) { - return sourceRectHint != null - && sourceRectHint.width() > destinationBounds.width() - && sourceRectHint.height() > destinationBounds.height(); + if (sourceRectHint == null || sourceRectHint.isEmpty()) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, empty hint"); + return false; + } + if (sourceRectHint.width() <= destinationBounds.width() + || sourceRectHint.height() <= destinationBounds.height()) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, hint(%s) is smaller" + + " than destination(%s)", sourceRectHint, destinationBounds); + return false; + } + final float reportedRatio = destinationBounds.width() / (float) destinationBounds.height(); + final float inferredRatio = sourceRectHint.width() / (float) sourceRectHint.height(); + if (Math.abs(reportedRatio - inferredRatio) > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, hint(%s) does not match" + + " destination(%s) aspect ratio", sourceRectHint, destinationBounds); + return false; + } + return true; } public float getDefaultAspectRatio() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index a09720dd6a70..3e9366fd6459 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -34,6 +34,7 @@ import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.Flags import com.android.wm.shell.protolog.ShellProtoLogGroup import kotlin.math.abs +import kotlin.math.roundToInt /** A class that includes convenience methods. */ object PipUtils { @@ -149,16 +150,16 @@ object PipUtils { val appBoundsAspRatio = appBounds.width().toFloat() / appBounds.height() val width: Int val height: Int - var left = 0 - var top = 0 + var left = appBounds.left + var top = appBounds.top if (appBoundsAspRatio < aspectRatio) { width = appBounds.width() - height = Math.round(width / aspectRatio) - top = (appBounds.height() - height) / 2 + height = (width / aspectRatio).roundToInt() + top = appBounds.top + (appBounds.height() - height) / 2 } else { height = appBounds.height() - width = Math.round(height * aspectRatio) - left = (appBounds.width() - width) / 2 + width = (height * aspectRatio).roundToInt() + left = appBounds.left + (appBounds.width() - width) / 2 } return Rect(left, top, left + width, top + height) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index c2242a8b87fa..2234041b8c9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -336,11 +336,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { setTouching(); mStartPos = touchPos; mMoving = false; - // This triggers initialization of things like the resize veil in preparation for - // showing it when the user moves the divider past the slop, and has to be done - // before onStartDragging() which starts the jank interaction tracing - mSplitLayout.updateDividerBounds(mSplitLayout.getDividerPosition(), - false /* shouldUseParallaxEffect */); mSplitLayout.onStartDragging(); break; case MotionEvent.ACTION_MOVE: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 87bd84017dee..1fcfa7fcf350 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.UserManager; import android.view.Choreographer; import android.view.IWindowManager; +import android.view.SurfaceControl; import android.view.WindowManager; import com.android.internal.jank.InteractionJankMonitor; @@ -400,7 +401,8 @@ public abstract class WMShellModule { Optional<RecentTasksController> recentTasksController, HomeTransitionObserver homeTransitionObserver) { return new RecentsTransitionHandler(shellInit, transitions, - recentTasksController.orElse(null), homeTransitionObserver); + recentTasksController.orElse(null), homeTransitionObserver, + SurfaceControl.Transaction::new); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 9192e6ed3175..fbc11c19a5a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -159,13 +159,24 @@ class DesktopModeEventLogger { } companion object { + /** + * Describes a task position and dimensions. + * + * @property instanceId instance id of the task + * @property uid uid of the app associated with the task + * @property taskHeight height of the task in px + * @property taskWidth width of the task in px + * @property taskX x-coordinate of the top-left corner + * @property taskY y-coordinate of the top-left corner + * + */ data class TaskUpdate( val instanceId: Int, val uid: Int, - val taskHeight: Int = Int.MIN_VALUE, - val taskWidth: Int = Int.MIN_VALUE, - val taskX: Int = Int.MIN_VALUE, - val taskY: Int = Int.MIN_VALUE, + val taskHeight: Int, + val taskWidth: Int, + val taskX: Int, + val taskY: Int, ) /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 641952b28bfb..e71056043d5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -282,17 +282,23 @@ class DesktopModeLoggerTransitionObserver( visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks) } - // TODO(b/326231724) - Add logging around taskInfoChanges Updates /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( sessionId: Int, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo> ) { - // find new tasks that were added postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> - if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) { - desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo)) + val currentTaskUpdate = buildTaskUpdateForTask(taskInfo) + val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId] + when { + // new tasks added + previousTaskInfo == null -> + desktopModeEventLogger.logTaskAdded(sessionId, currentTaskUpdate) + // old tasks that were resized or repositioned + // TODO(b/347935387): Log changes only once they are stable. + buildTaskUpdateForTask(previousTaskInfo) != currentTaskUpdate -> + desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate) } } @@ -304,13 +310,17 @@ class DesktopModeLoggerTransitionObserver( } } - // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate { - val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId) - // add task x, y if available - taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) } - - return taskUpdate + val screenBounds = taskInfo.configuration.windowConfiguration.bounds + val positionInParent = taskInfo.positionInParent + return TaskUpdate( + instanceId = taskInfo.taskId, + uid = taskInfo.userId, + taskHeight = screenBounds.height(), + taskWidth = screenBounds.width(), + taskX = positionInParent.x, + taskY = positionInParent.y, + ) } /** Get [EnterReason] for this session enter */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 7d01580ecb6e..81891ce91e04 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -46,6 +46,9 @@ class DesktopModeTaskRepository { val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), val minimizedTasks: ArraySet<Int> = ArraySet(), + // Tasks that are closing, but are still visible + // TODO(b/332682201): Remove when the repository state is updated via TransitionObserver + val closingTasks: ArraySet<Int> = ArraySet(), // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), ) @@ -169,6 +172,42 @@ class DesktopModeTaskRepository { return result } + /** + * Mark a task with given [taskId] as closing on given [displayId] + * + * @return `true` if the task was not closing on given [displayId] + */ + fun addClosingTask(displayId: Int, taskId: Int): Boolean { + val added = displayData.getOrCreate(displayId).closingTasks.add(taskId) + if (added) { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: added closing task=%d displayId=%d", + taskId, + displayId + ) + } + return added + } + + /** + * Remove task with given [taskId] from closing tasks. + * + * @return `true` if the task was closing + */ + fun removeClosingTask(taskId: Int): Boolean { + var removed = false + displayData.forEach { _, data -> + if (data.closingTasks.remove(taskId)) { + removed = true + } + } + if (removed) { + KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId) + } + return removed + } + /** Check if a task with the given [taskId] was marked as an active task */ fun isActiveTask(taskId: Int): Boolean { return displayData.valueIterator().asSequence().any { data -> @@ -176,6 +215,10 @@ class DesktopModeTaskRepository { } } + /** Check if a task with the given [taskId] was marked as a closing task */ + fun isClosingTask(taskId: Int): Boolean = + displayData.valueIterator().asSequence().any { data -> taskId in data.closingTasks } + /** Whether a task is visible. */ fun isVisibleTask(taskId: Int): Boolean { return displayData.valueIterator().asSequence().any { data -> @@ -190,12 +233,17 @@ class DesktopModeTaskRepository { } } - /** Check if a task with the given [taskId] is the only active task on its display */ - fun isOnlyActiveTask(taskId: Int): Boolean { - return displayData.valueIterator().asSequence().any { data -> - data.activeTasks.singleOrNull() == taskId + /** + * Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task + * on its display + */ + fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean = + displayData.valueIterator().asSequence().any { data -> + data.visibleTasks + .subtract(data.closingTasks) + .subtract(data.minimizedTasks) + .singleOrNull() == taskId } - } /** Get a set of the active tasks for given [displayId] */ fun getActiveTasks(displayId: Int): ArraySet<Int> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index c5111d68881d..14ae3a7b9b27 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -40,7 +40,6 @@ import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN -import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.RemoteTransition import android.window.TransitionInfo @@ -76,6 +75,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.shared.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.DesktopModeStatus.useDesktopOverrideDensity +import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.splitscreen.SplitScreenController @@ -442,12 +442,21 @@ class DesktopTasksController( * active task. * * @param wct transaction to modify if the last active task is closed + * @param displayId display id of the window that's being closed * @param taskId task id of the window that's being closed */ - fun onDesktopWindowClose(wct: WindowContainerTransaction, taskId: Int) { - if (desktopModeTaskRepository.isOnlyActiveTask(taskId)) { + fun onDesktopWindowClose(wct: WindowContainerTransaction, displayId: Int, taskId: Int) { + if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskId)) { removeWallpaperActivity(wct) } + if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) { + // Could happen if the task hasn't been removed from closing list after it disappeared + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: the task with taskId=%d is already closing!", + taskId + ) + } } /** Move a task with given `taskId` to fullscreen */ @@ -870,8 +879,8 @@ class DesktopTasksController( reason = "recents animation is running" false } - // Handle back navigation for the last window if wallpaper available - shouldRemoveWallpaper(request) -> true + // Handle task closing for the last window if wallpaper is available + shouldHandleTaskClosing(request) -> true // Only handle open or to front transitions request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> { reason = "transition type not handled (${request.type})" @@ -909,7 +918,8 @@ class DesktopTasksController( val result = triggerTask?.let { task -> when { - request.type == TRANSIT_TO_BACK -> handleBackNavigation(task) + // Check if the closing task needs to be handled + TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task) // Check if the task has a top transparent activity shouldLaunchAsModal(task) -> handleIncompatibleTaskLaunch(task) // Check if the task has a top systemUI activity @@ -951,13 +961,10 @@ class DesktopTasksController( private fun shouldLaunchAsModal(task: TaskInfo) = Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) - private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean { + private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean { return Flags.enableDesktopWindowingWallpaperActivity() && - request.type == TRANSIT_TO_BACK && - request.triggerTask?.let { task -> - desktopModeTaskRepository.isOnlyActiveTask(task.taskId) - } - ?: false + TransitionUtil.isClosingType(request.type) && + request.triggerTask != null } private fun handleFreeformTaskLaunch( @@ -1024,17 +1031,26 @@ class DesktopTasksController( return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) } } - /** Handle back navigation by removing wallpaper activity if it's the last active task */ - private fun handleBackNavigation(task: RunningTaskInfo): WindowContainerTransaction? { - if ( - desktopModeTaskRepository.isOnlyActiveTask(task.taskId) && + /** Handle task closing by removing wallpaper activity if it's the last active task */ + private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? { + val wct = if ( + desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId) && desktopModeTaskRepository.wallpaperActivityToken != null ) { // Remove wallpaper activity when the last active task is removed - return WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) } + WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) } } else { - return null + null + } + if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) { + // Could happen if the task hasn't been removed from closing list after it disappeared + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: the task with taskId=%d is already closing!", + task.taskId + ) } + return wct } private fun addMoveToDesktopChanges( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt index 88d0554669b7..5335c0b69a24 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt @@ -27,6 +27,8 @@ import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.core.animation.addListener +import com.android.internal.jank.Cuj +import com.android.wm.shell.common.InteractionJankMonitorUtils import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener @@ -103,6 +105,8 @@ class ToggleResizeDesktopTaskTransitionHandler( onTaskResizeAnimationListener.onAnimationEnd(taskId) finishCallback.onTransitionFinished(null) boundsAnimator = null + InteractionJankMonitorUtils.endTracing( + Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW) } ) addUpdateListener { anim -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md index 438aa768165e..b1cbe8d98397 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md @@ -73,7 +73,7 @@ stack traces when specific surface transaction calls are made, which is possible following system properties for example: ```shell # Enabling -adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha # matches the name of the SurfaceControlTransaction method +adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,setPosition # matches the name of the SurfaceControlTransaction methods adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface adb reboot adb logcat -s "SurfaceControlRegistry" @@ -87,6 +87,16 @@ adb reboot It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite noisy if unfiltered. +It can sometimes be useful to trace specific logs and when they are applied (sometimes we build +transactions that can be applied later). You can do this by adding the "merge" and "apply" calls to +the set of requested calls: +```shell +# Enabling +adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,merge,apply # apply will dump logs of each setAlpha or merge call on that tx +adb reboot +adb logcat -s "SurfaceControlRegistry" +``` + ## Tracing activity starts in the app process It's sometimes useful to know when to see a stack trace of when an activity starts in the app code diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 7d2aa275a684..b48aee5ccd5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -150,6 +150,10 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); } + } else if (repository.isClosingTask(taskInfo.taskId) + && repository.removeClosingTask(taskInfo.taskId)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "Removing closing freeform task: #%d", taskInfo.taskId); } repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index 202f60dad842..3d1994cac534 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -137,7 +137,7 @@ public class PipSurfaceTransactionHelper { mTmpDestinationRect.inset(insets); // Scale to the bounds no smaller than the destination and offset such that the top/left // of the scaled inset source rect aligns with the top/left of the destination bounds - final float scale, left, top; + final float scale; if (isInPipDirection && sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) { // scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only. @@ -148,14 +148,17 @@ public class PipSurfaceTransactionHelper { ? (float) destinationBounds.width() / sourceBounds.width() : (float) destinationBounds.height() / sourceBounds.height(); scale = (1 - fraction) * startScale + fraction * endScale; - left = destinationBounds.left - insets.left * scale; - top = destinationBounds.top - insets.top * scale; } else { scale = Math.max((float) destinationBounds.width() / sourceBounds.width(), (float) destinationBounds.height() / sourceBounds.height()); - // Work around the rounding error by fix the position at very beginning. - left = scale == 1 ? 0 : destinationBounds.left - insets.left * scale; - top = scale == 1 ? 0 : destinationBounds.top - insets.top * scale; + } + float left = destinationBounds.left - insets.left * scale; + float top = destinationBounds.top - insets.top * scale; + if (scale == 1) { + // Work around the 1 pixel off error by rounding the position down at very beginning. + // We noticed such error from flicker tests, not visually. + left = sourceBounds.left; + top = sourceBounds.top; } mTmpTransform.setScale(scale, scale); tx.setMatrix(leash, mTmpTransform, mTmpFloat9) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index e4420d73886e..e2e1ecde8b56 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -63,7 +63,6 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemProperties; -import android.util.Rational; import android.view.Choreographer; import android.view.Display; import android.view.Surface; @@ -128,8 +127,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, SystemProperties.getInt( "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400); - private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.005f; - private final Context mContext; private final SyncTransactionQueue mSyncTransactionQueue; private final PipBoundsState mPipBoundsState; @@ -608,6 +605,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, public void exitPip(int animationDurationMs, boolean requestEnterSplit) { if (!mPipTransitionState.isInPip() || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP + || mPipTransitionState.getInSwipePipToHomeTransition() || mToken == null) { ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Not allowed to exitPip in current state" @@ -821,37 +819,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPictureInPictureParams.getTitle()); mPipParamsChangedForwarder.notifySubtitleChanged( mPictureInPictureParams.getSubtitle()); - - if (mPictureInPictureParams.hasSourceBoundsHint() - && mPictureInPictureParams.hasSetAspectRatio()) { - Rational sourceRectHintAspectRatio = new Rational( - mPictureInPictureParams.getSourceRectHint().width(), - mPictureInPictureParams.getSourceRectHint().height()); - if (sourceRectHintAspectRatio.compareTo( - mPictureInPictureParams.getAspectRatio()) != 0) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "Aspect ratio of source rect hint (%d/%d) does not match the provided " - + "aspect ratio value (%d/%d). Consider matching them for " - + "improved animation. Future releases might override the " - + "value to match.", - mPictureInPictureParams.getSourceRectHint().width(), - mPictureInPictureParams.getSourceRectHint().height(), - mPictureInPictureParams.getAspectRatio().getNumerator(), - mPictureInPictureParams.getAspectRatio().getDenominator()); - } - if (Math.abs(sourceRectHintAspectRatio.floatValue() - - mPictureInPictureParams.getAspectRatioFloat()) - > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "Aspect ratio of source rect hint (%f) does not match the provided " - + "aspect ratio value (%f) and is above threshold of %f. " - + "Consider matching them for improved animation. Future " - + "releases might override the value to match.", - sourceRectHintAspectRatio.floatValue(), - mPictureInPictureParams.getAspectRatioFloat(), - PIP_ASPECT_RATIO_MISMATCH_THRESHOLD); - } - } } mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 03c8cf8cc795..9f3c519b441b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -409,10 +409,6 @@ public class RecentTasksController implements TaskStackListenerCallback, if (DesktopModeStatus.canEnterDesktopMode(mContext) && mDesktopModeTaskRepository.isPresent() && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) { - if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) { - // Minimized freeform tasks should not be shown at all. - continue; - } // Freeform tasks will be added as a separate entry if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) { mostRecentFreeformTaskIndex = recentTasks.size(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 3a266d9bb3ef..c67cf1d85918 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -74,6 +74,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.function.Consumer; +import java.util.function.Supplier; /** * Handles the Recents (overview) animation. Only one of these can run at a time. A recents @@ -84,6 +85,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private final Transitions mTransitions; private final ShellExecutor mExecutor; + private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; @Nullable private final RecentTasksController mRecentTasksController; private IApplicationThread mAnimApp = null; @@ -101,11 +103,13 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, @Nullable RecentTasksController recentTasksController, - HomeTransitionObserver homeTransitionObserver) { + HomeTransitionObserver homeTransitionObserver, + Supplier<SurfaceControl.Transaction> transactionSupplier) { mTransitions = transitions; mExecutor = transitions.getMainExecutor(); mRecentTasksController = recentTasksController; mHomeTransitionObserver = homeTransitionObserver; + mTransactionSupplier = transactionSupplier; if (!Transitions.ENABLE_SHELL_TRANSITIONS) return; if (recentTasksController == null) return; shellInit.addInitCallback(() -> { @@ -1056,7 +1060,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { final Transitions.TransitionFinishCallback finishCB = mFinishCB; mFinishCB = null; - final SurfaceControl.Transaction t = mFinishTransaction; + SurfaceControl.Transaction t = mFinishTransaction; final WindowContainerTransaction wct = new WindowContainerTransaction(); if (mKeyguardLocked && mRecentsTask != null) { @@ -1106,6 +1110,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish"); + if (toHome && !mOpeningTasks.isEmpty()) { + // Attempting to start a task after swipe to home, don't show it, + // move recents to top + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " attempting to start a task after swipe to home"); + t = mTransactionSupplier.get(); + wct.reorder(mRecentsTask, true /*onTop*/); + mClosingTasks.addAll(mOpeningTasks); + mOpeningTasks.clear(); + } // The general case: committing to recents, going home, or switching tasks. for (int i = 0; i < mOpeningTasks.size(); ++i) { t.show(mOpeningTasks.get(i).mTaskSurface); @@ -1174,6 +1188,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mPipTransaction = null; } } + if (t != mFinishTransaction) { + // apply after merges because these changes are accounting for finishWCT changes. + mTransitions.setAfterMergeFinishTransaction(mTransition, t); + } cleanUp(); finishCB.onTransitionFinished(wct.isEmpty() ? null : wct); if (runnerFinishCb != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index b6a18e537600..45eff4a24898 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2649,7 +2649,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable TransitionRequestInfo request) { final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { - if (isSplitActive()) { + if (isSplitScreenVisible()) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation", request.getDebugId()); // Check if the display is rotating. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 66b3553bea09..8fc54edcbd4b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -21,8 +21,6 @@ import static android.graphics.Color.WHITE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static com.android.window.flags.Flags.windowSessionRelayoutInfo; - import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,7 +28,6 @@ import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.graphics.Paint; import android.graphics.Rect; -import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; @@ -139,16 +136,10 @@ public class TaskSnapshotWindow { } try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout"); - if (windowSessionRelayoutInfo()) { - final WindowRelayoutResult outRelayoutResult = new WindowRelayoutResult(tmpFrames, - tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls); - session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0, - outRelayoutResult); - } else { - session.relayoutLegacy(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0, - tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState, - tmpControls, new Bundle()); - } + final WindowRelayoutResult outRelayoutResult = new WindowRelayoutResult(tmpFrames, + tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls); + session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0, + outRelayoutResult); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } catch (RemoteException e) { snapshotSurface.clearWindowSynced(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java index b03daaafd70c..35427b93acea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java @@ -94,6 +94,11 @@ public class CounterRotatorHelper { return rotatedBounds; } + /** Returns true if the change is put on a surface in previous rotation. */ + public boolean isRotated(@NonNull TransitionInfo.Change change) { + return mLastRotationDelta != 0 && mRotatorMap.containsKey(change.getParent()); + } + /** * Removes the counter rotation surface in the finish transaction. No need to reparent the * children as the finish transaction should have already taken care of that. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 018c9044e2f7..9412b2b0b243 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -517,7 +517,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { animRelOffset.y = Math.max(animRelOffset.y, change.getEndRelOffset().y); } - if (change.getActivityComponent() != null && !isActivityLevel) { + if (change.getActivityComponent() != null && !isActivityLevel + && !mRotator.isRotated(change)) { // At this point, this is an independent activity change in a non-activity // transition. This means that an activity transition got erroneously combined // with another ongoing transition. This then means that the animation root may diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index f257e207673d..d2760ff88ece 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -238,6 +238,13 @@ public class Transitions implements RemoteCallable<Transitions>, /** Ordered list of transitions which have been merged into this one. */ private ArrayList<ActiveTransition> mMerged; + /** + * @deprecated DO NOT USE THIS unless absolutely necessary. It will be removed once + * everything migrates off finishWCT. + */ + @java.lang.Deprecated + SurfaceControl.Transaction mAfterMergeFinishT; + ActiveTransition(IBinder token) { mToken = token; } @@ -1018,6 +1025,20 @@ public class Transitions implements RemoteCallable<Transitions>, return null; } + /** @deprecated */ + @java.lang.Deprecated + public void setAfterMergeFinishTransaction(IBinder transition, + SurfaceControl.Transaction afterMergeFinishT) { + final ActiveTransition at = mKnownTransitions.get(transition); + if (at == null) return; + if (at.mAfterMergeFinishT != null) { + Log.e(TAG, "Setting after-merge-t >1 time on transition: " + at.mInfo.getDebugId()); + at.mAfterMergeFinishT.merge(afterMergeFinishT); + return; + } + at.mAfterMergeFinishT = afterMergeFinishT; + } + /** Aborts a transition. This will still queue it up to maintain order. */ private void onAbort(ActiveTransition transition) { final Track track = mTracks.get(transition.getTrack()); @@ -1078,6 +1099,7 @@ public class Transitions implements RemoteCallable<Transitions>, } // Merge all associated transactions together SurfaceControl.Transaction fullFinish = active.mFinishT; + SurfaceControl.Transaction afterMergeFinish = active.mAfterMergeFinishT; if (active.mMerged != null) { for (int iM = 0; iM < active.mMerged.size(); ++iM) { final ActiveTransition toMerge = active.mMerged.get(iM); @@ -1097,6 +1119,21 @@ public class Transitions implements RemoteCallable<Transitions>, fullFinish.merge(toMerge.mFinishT); } } + if (toMerge.mAfterMergeFinishT != null) { + if (afterMergeFinish == null) { + afterMergeFinish = toMerge.mAfterMergeFinishT; + } else { + afterMergeFinish.merge(toMerge.mAfterMergeFinishT); + } + toMerge.mAfterMergeFinishT = null; + } + } + } + if (afterMergeFinish != null) { + if (fullFinish == null) { + fullFinish = afterMergeFinish; + } else { + fullFinish.merge(afterMergeFinish); } } if (fullFinish != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 21b6db29143a..e1009a0ae8bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -73,6 +73,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.Cuj; import com.android.internal.protolog.common.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.R; @@ -81,6 +82,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.InteractionJankMonitorUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource; @@ -435,7 +437,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SplitScreenController.EXIT_REASON_DESKTOP_MODE); } else { WindowContainerTransaction wct = new WindowContainerTransaction(); - mDesktopTasksController.onDesktopWindowClose(wct, mTaskId); + mDesktopTasksController.onDesktopWindowClose(wct, mDisplayId, mTaskId); mTaskOperations.closeTask(mTaskToken, wct); } } else if (id == R.id.back_button) { @@ -470,11 +472,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.collapse_menu_button) { decoration.closeHandleMenu(); } else if (id == R.id.maximize_window) { + InteractionJankMonitorUtils.beginTracing( + Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, /* view= */ v, + /* tag= */ "caption_bar_button"); final RunningTaskInfo taskInfo = decoration.mTaskInfo; decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); mDesktopTasksController.toggleDesktopTaskSize(taskInfo); } else if (id == R.id.maximize_menu_maximize_button) { + InteractionJankMonitorUtils.beginTracing( + Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, /* view= */ v, + /* tag= */ "maximize_menu_option"); final RunningTaskInfo taskInfo = decoration.mTaskInfo; mDesktopTasksController.toggleDesktopTaskSize(taskInfo); decoration.closeHandleMenu(); @@ -712,6 +720,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + InteractionJankMonitorUtils.beginTracing( + Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, mContext, + /* surface= */ decoration.mTaskSurface, /* tag= */ "double_tap"); mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java index fe1c9c3cce66..d48ce536f2b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java @@ -28,6 +28,8 @@ import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.SurfaceControl; +import androidx.annotation.NonNull; + import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayController; @@ -106,13 +108,15 @@ public class DragPositioningCallbackUtility { repositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom) ? candidateBottom : oldBottom; } - // If width or height are negative or less than the minimum width or height, revert the + // If width or height are negative or exceeding the width or height constraints, revert the // respective bounds to use previous bound dimensions. - if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) { + if (isExceedingWidthConstraint(repositionTaskBounds, stableBounds, displayController, + windowDecoration)) { repositionTaskBounds.right = oldRight; repositionTaskBounds.left = oldLeft; } - if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) { + if (isExceedingHeightConstraint(repositionTaskBounds, stableBounds, displayController, + windowDecoration)) { repositionTaskBounds.top = oldTop; repositionTaskBounds.bottom = oldBottom; } @@ -174,6 +178,30 @@ public class DragPositioningCallbackUtility { return result; } + private static boolean isExceedingWidthConstraint(@NonNull Rect repositionTaskBounds, + Rect maxResizeBounds, DisplayController displayController, + WindowDecoration windowDecoration) { + // Check if width is less than the minimum width constraint. + if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) { + return true; + } + // Check if width is more than the maximum resize bounds on desktop windowing mode. + return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext) + && repositionTaskBounds.width() > maxResizeBounds.width(); + } + + private static boolean isExceedingHeightConstraint(@NonNull Rect repositionTaskBounds, + Rect maxResizeBounds, DisplayController displayController, + WindowDecoration windowDecoration) { + // Check if height is less than the minimum height constraint. + if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) { + return true; + } + // Check if height is more than the maximum resize bounds on desktop windowing mode. + return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext) + && repositionTaskBounds.height() > maxResizeBounds.height(); + } + private static float getMinWidth(DisplayController displayController, WindowDecoration windowDecoration) { return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController, @@ -210,7 +238,7 @@ public class DragPositioningCallbackUtility { private static float getDefaultMinSize(DisplayController displayController, WindowDecoration windowDecoration) { - float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId) + float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId) .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE; return windowDecoration.mTaskInfo.defaultMinSize * density; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 5fce5d228d71..956d04c548f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -18,6 +18,8 @@ package com.android.wm.shell.windowdecor; import static android.view.WindowManager.TRANSIT_CHANGE; +import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW; + import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -33,6 +35,7 @@ import androidx.annotation.Nullable; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.InteractionJankMonitorUtils; import com.android.wm.shell.transition.Transitions; import java.util.function.Supplier; @@ -89,6 +92,10 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); mRepositionStartPoint.set(x, y); if (isResizing()) { + // Capture CUJ for re-sizing window in DW mode. + InteractionJankMonitorUtils.beginTracing(CUJ_DESKTOP_MODE_RESIZE_WINDOW, + mDesktopWindowDecoration.mContext, mDesktopWindowDecoration.mTaskSurface, + /* tag= */ null); if (!mDesktopWindowDecoration.mTaskInfo.isFocused) { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true); @@ -146,6 +153,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, // won't be called. resetVeilIfVisible(); } + InteractionJankMonitorUtils.endTracing(CUJ_DESKTOP_MODE_RESIZE_WINDOW); } else { final WindowContainerTransaction wct = new WindowContainerTransaction(); DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp index 0fe7a16be851..3f2603aec86a 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp @@ -37,17 +37,51 @@ filegroup { "src/**/B*.kt", "src/**/C*.kt", "src/**/D*.kt", - "src/**/E*.kt", ], } filegroup { name: "WMShellFlickerTestsSplitScreenGroup2-src", srcs: [ + "src/**/E*.kt", + ], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreenGroup3-src", + srcs: [ + "src/**/S*.kt", + ], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreenGroupOther-src", + srcs: [ "src/**/*.kt", ], } +java_library { + name: "WMShellFlickerTestsSplitScreenBase", + srcs: [ + ":WMShellFlickerTestsSplitScreenBase-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "wm-shell-flicker-utils", + "androidx.test.ext.junit", + "flickertestapplib", + "flickerlib", + "flickerlib-helpers", + "flickerlib-trace_processor_shell", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", + "launcher-helper-lib", + "launcher-aosp-tapl", + ], +} + android_test { name: "WMShellFlickerTestsSplitScreenGroup1", defaults: ["WMShellFlickerTestsDefault"], @@ -56,10 +90,12 @@ android_test { instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", test_config_template: "AndroidTestTemplate.xml", srcs: [ - ":WMShellFlickerTestsSplitScreenBase-src", ":WMShellFlickerTestsSplitScreenGroup1-src", ], - static_libs: ["WMShellFlickerTestsBase"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", + ], data: ["trace_config/*"], } @@ -71,12 +107,50 @@ android_test { instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", test_config_template: "AndroidTestTemplate.xml", srcs: [ - ":WMShellFlickerTestsSplitScreenBase-src", - ":WMShellFlickerTestsSplitScreenGroup2-src", + ":WMShellFlickerTestsSplitScreenGroup1-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", + ], + data: ["trace_config/*"], +} + +android_test { + name: "WMShellFlickerTestsSplitScreenGroup3", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + test_config_template: "AndroidTestTemplate.xml", + srcs: [ + ":WMShellFlickerTestsSplitScreenGroup1-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", + ], + data: ["trace_config/*"], +} + +android_test { + name: "WMShellFlickerTestsSplitScreenGroupOther", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + test_config_template: "AndroidTestTemplate.xml", + srcs: [ + ":WMShellFlickerTestsSplitScreenGroupOther-src", ], exclude_srcs: [ ":WMShellFlickerTestsSplitScreenGroup1-src", + ":WMShellFlickerTestsSplitScreenGroup2-src", + ":WMShellFlickerTestsSplitScreenGroup3-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", ], - static_libs: ["WMShellFlickerTestsBase"], data: ["trace_config/*"], } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 13f95ccea640..92be4f9f0374 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -46,6 +46,7 @@ android_test { "androidx.dynamicanimation_dynamicanimation", "dagger2", "frameworks-base-testutils", + "kotlin-test", "kotlinx-coroutines-android", "kotlinx-coroutines-core", "mockito-kotlin2", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index bd20c1143262..731f75bf9f5d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -20,9 +20,11 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; +import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds; import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -32,6 +34,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.animation.Animator; +import android.annotation.NonNull; +import android.graphics.Point; +import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; @@ -130,7 +135,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim @Test public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) - .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) + .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) .build(); info.setAnimationOptions(TransitionInfo.AnimationOptions .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, @@ -148,7 +153,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim @Test public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) - .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) + .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) .build(); info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, @@ -161,4 +166,128 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim // An invalid custom animation is equivalent to jump-cut. assertEquals(0, animator.getDuration()); } + + @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) + @Test + public void testCalculateParentBounds_flagDisabled() { + final Rect parentBounds = new Rect(0, 0, 2000, 2000); + final Rect primaryBounds = new Rect(); + final Rect secondaryBounds = new Rect(); + parentBounds.splitVertically(primaryBounds, secondaryBounds); + + final TransitionInfo.Change change = createChange(0 /* flags */); + change.setStartAbsBounds(secondaryBounds); + + final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */); + boundsAnimationChange.setStartAbsBounds(primaryBounds); + boundsAnimationChange.setEndAbsBounds(primaryBounds); + final Rect actualParentBounds = new Rect(); + + calculateParentBounds(change, boundsAnimationChange, actualParentBounds); + + assertEquals(parentBounds, actualParentBounds); + + actualParentBounds.setEmpty(); + + boundsAnimationChange.setStartAbsBounds(secondaryBounds); + boundsAnimationChange.setEndAbsBounds(primaryBounds); + + calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds); + + assertEquals(parentBounds, actualParentBounds); + } + + // TODO(b/243518738): Rewrite with TestParameter + @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) + @Test + public void testCalculateParentBounds_flagEnabled() { + TransitionInfo.Change change; + final TransitionInfo.Change stubChange = createChange(0 /* flags */); + final Rect actualParentBounds = new Rect(); + Rect parentBounds = new Rect(0, 0, 2000, 2000); + Rect endAbsBounds = new Rect(0, 0, 2000, 2000); + change = prepareChangeForParentBoundsCalculationTest( + new Point(0, 0) /* endRelOffset */, + endAbsBounds, + new Point() /* endParentSize */ + ); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertTrue("Parent bounds must be empty because end parent size is not set.", + actualParentBounds.isEmpty()); + + String testString = "Parent start with (0, 0)"; + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + + testString = "Container not start with (0, 0)"; + parentBounds = new Rect(0, 0, 2000, 2000); + endAbsBounds = new Rect(1000, 500, 2000, 1500); + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + + testString = "Parent container on the right"; + parentBounds = new Rect(1000, 0, 2000, 2000); + endAbsBounds = new Rect(1000, 500, 1500, 1500); + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + + testString = "Parent container on the bottom"; + parentBounds = new Rect(0, 1000, 2000, 2000); + endAbsBounds = new Rect(500, 1500, 1500, 2000); + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + + testString = "Parent container in the middle"; + parentBounds = new Rect(500, 500, 1500, 1500); + endAbsBounds = new Rect(1000, 500, 1500, 1000); + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + } + + @NonNull + private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest( + @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) { + final TransitionInfo.Change change = createChange(0 /* flags */); + change.setEndRelOffset(endRelOffset.x, endRelOffset.y); + change.setEndAbsBounds(endAbsBounds); + change.setEndParentSize(endParentSize.x, endParentSize.y); + return change; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java index 0b2265d4ce9c..c18d7ec821b6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; +import static android.view.WindowManager.TRANSIT_NONE; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; @@ -31,6 +32,7 @@ import android.annotation.NonNull; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.TransitionInfo; import android.window.WindowContainerToken; @@ -82,11 +84,27 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { spyOn(mFinishCallback); } - /** Creates a mock {@link TransitionInfo.Change}. */ + /** + * Creates a mock {@link TransitionInfo.Change}. + * + * @param flags the {@link TransitionInfo.ChangeFlags} of the change + */ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) { + return createChange(flags, TRANSIT_NONE); + } + + /** + * Creates a mock {@link TransitionInfo.Change}. + * + * @param flags the {@link TransitionInfo.ChangeFlags} of the change + * @param mode the transition mode of the change + */ + static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags, + @WindowManager.TransitionType int mode) { TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class), mock(SurfaceControl.class)); c.setFlags(flags); + c.setMode(mode); return c; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index fb03f20f939c..665bed0c8a88 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -19,6 +19,8 @@ import android.app.ActivityManager import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.Context +import android.graphics.Point +import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.SurfaceControl @@ -36,11 +38,12 @@ import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.WindowContainerToken import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT @@ -53,22 +56,23 @@ import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions -import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertNotNull +import junit.framework.Assert.assertNull import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.mock -import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.eq +import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.same +import org.mockito.kotlin.spy import org.mockito.kotlin.times +import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever /** * Test class for {@link DesktopModeLoggerTransitionObserver} @@ -77,20 +81,17 @@ import org.mockito.kotlin.verifyZeroInteractions */ @SmallTest @RunWith(AndroidTestingRunner::class) -class DesktopModeLoggerTransitionObserverTest { +class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @JvmField @Rule val extendedMockitoRule = - ExtendedMockitoRule.Builder(this) - .mockStatic(DesktopModeEventLogger::class.java) - .mockStatic(DesktopModeStatus::class.java) - .build()!! + ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!! - @Mock lateinit var testExecutor: ShellExecutor - @Mock private lateinit var mockShellInit: ShellInit - @Mock private lateinit var transitions: Transitions - @Mock private lateinit var context: Context + private val testExecutor = mock<ShellExecutor>() + private val mockShellInit = mock<ShellInit>() + private val transitions = mock<Transitions>() + private val context = mock<Context>() private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver private lateinit var shellInit: ShellInit @@ -98,9 +99,9 @@ class DesktopModeLoggerTransitionObserverTest { @Before fun setup() { - doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) } - shellInit = Mockito.spy(ShellInit(testExecutor)) - desktopModeEventLogger = mock(DesktopModeEventLogger::class.java) + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + shellInit = spy(ShellInit(testExecutor)) + desktopModeEventLogger = mock<DesktopModeEventLogger>() transitionObserver = DesktopModeLoggerTransitionObserver( @@ -121,7 +122,7 @@ class DesktopModeLoggerTransitionObserverTest { @Test fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() { - val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) @@ -132,22 +133,17 @@ class DesktopModeLoggerTransitionObserverTest { @Test fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() { - val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FREEFORM_INTENT)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT, DEFAULT_TASK_UPDATE) } @Test fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) // task change is finalised when drag ends val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0) @@ -155,82 +151,57 @@ class DesktopModeLoggerTransitionObserverTest { .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_DRAG)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE) } @Test fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0) .addChange(change) .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_MENU_BUTTON)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON, DEFAULT_TASK_UPDATE) } @Test fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) .addChange(change) .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE) } @Test fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0) .addChange(change) .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.KEYBOARD_SHORTCUT_ENTER)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER, DEFAULT_TASK_UPDATE) } @Test fun transitToFront_logTaskAddedAndEnterReasonOverview() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test @@ -238,35 +209,29 @@ class DesktopModeLoggerTransitionObserverTest { // previous exit to overview transition val previousSessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) transitionObserver.setLoggerSessionId(previousSessionId) - val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(previousChange) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) .build() callOnTransitionReady(previousTransitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + verifyTaskRemovedAndExitLogging( + previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows // TRANSIT_TO_FRONT - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test @@ -274,35 +239,29 @@ class DesktopModeLoggerTransitionObserverTest { // previous exit to overview transition val previousSessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) transitionObserver.setLoggerSessionId(previousSessionId) - val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(previousChange) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) .build() callOnTransitionReady(previousTransitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + verifyTaskRemovedAndExitLogging( + previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows // TRANSIT_CHANGE - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test @@ -310,35 +269,29 @@ class DesktopModeLoggerTransitionObserverTest { // previous exit to overview transition val previousSessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) transitionObserver.setLoggerSessionId(previousSessionId) - val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(previousChange) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) .build() callOnTransitionReady(previousTransitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + verifyTaskRemovedAndExitLogging( + previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows // TRANSIT_OPEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test @@ -349,286 +302,389 @@ class DesktopModeLoggerTransitionObserverTest { // previous exit to overview transition val previousSessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) transitionObserver.setLoggerSessionId(previousSessionId) - val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val previousTransitionInfo = - TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(previousChange) - .build() + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) + .build() callOnTransitionReady(previousTransitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + verifyTaskRemovedAndExitLogging( + previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = - TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) - .addChange(change) - .build() + TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) + .addChange(change) + .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE) } @Test fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.UNKNOWN_ENTER)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER, DEFAULT_TASK_UPDATE) } @Test fun transitWake_logTaskAddedAndEnterReasonScreenOn() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.SCREEN_ON)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE) } @Test - fun transitSleep_logTaskAddedAndExitReasonScreenOff_sessionIdNull() { + fun transitSleep_logTaskRemovedAndExitReasonScreenOff_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.SCREEN_OFF)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) } @Test fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.DRAG_TO_EXIT)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging(sessionId, ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE) } @Test fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON) .addChange(change) .build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging( + sessionId, ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE) } @Test fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.KEYBOARD_SHORTCUT_EXIT)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging( + sessionId, ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE) } @Test fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.UNKNOWN_EXIT)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging(sessionId, ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE) } @Test fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // recents transition - val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging( + sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) } @Test fun transitClose_logTaskRemovedAndExitReasonTaskFinished_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // task closing - val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.TASK_FINISHED)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging(sessionId, ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE) } @Test fun sessionExitByRecents_cancelledAnimation_sessionRestored() { val sessionId = 1 // add a freeform task to an existing session - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) transitionObserver.setLoggerSessionId(sessionId) // recents transition sent freeform window to back - val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_BACK, taskInfo) val transitionInfo1 = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build() callOnTransitionReady(transitionInfo1) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + + verifyTaskRemovedAndExitLogging( + sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build() callOnTransitionReady(transitionInfo2) - verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any()) - verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any()) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() { val sessionId = 1 // add an existing freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // new freeform task added - val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)) + .logTaskAdded(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2))) verify(desktopModeEventLogger, never()).logSessionEnter(any(), any()) } @Test + fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() { + val sessionId = 1 + // add an existing freeform task + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) + transitionObserver.setLoggerSessionId(sessionId) + + // task position changed + val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo)) + .build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100))) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun sessionAlreadyStarted_taskResized_logsTaskUpdate() { + val sessionId = 1 + // add an existing freeform task + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) + transitionObserver.setLoggerSessionId(sessionId) + + // task resized + val newTaskInfo = + createTaskInfo( + WINDOWING_MODE_FREEFORM, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo)) + .build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(sessionId), + eq( + DEFAULT_TASK_UPDATE.copy( + taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100))) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() { + val sessionId = 1 + // add 2 existing freeform task + val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM) + val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) + transitionObserver.addTaskInfosToCachedMap(taskInfo1) + transitionObserver.addTaskInfosToCachedMap(taskInfo2) + transitionObserver.setLoggerSessionId(sessionId) + + // task 1 position update + val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) + val transitionInfo1 = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo1)) + .build() + callOnTransitionReady(transitionInfo1) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100))) + verifyZeroInteractions(desktopModeEventLogger) + + // task 2 resize + val newTaskInfo2 = + createTaskInfo( + WINDOWING_MODE_FREEFORM, + id = 2, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100) + val transitionInfo2 = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo2)) + .build() + + callOnTransitionReady(transitionInfo2) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(sessionId), + eq( + DEFAULT_TASK_UPDATE.copy( + instanceId = 2, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100))) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() { val sessionId = 1 // add two existing freeform tasks - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) transitionObserver.setLoggerSessionId(sessionId) - // new freeform task added - val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + // new freeform task closed + val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)) + .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2))) verify(desktopModeEventLogger, never()).logSessionExit(any(), any()) } /** Simulate calling the onTransitionReady() method */ private fun callOnTransitionReady(transitionInfo: TransitionInfo) { - val transition = mock(IBinder::class.java) - val startT = mock(SurfaceControl.Transaction::class.java) - val finishT = mock(SurfaceControl.Transaction::class.java) + val transition = mock<IBinder>() + val startT = mock<SurfaceControl.Transaction>() + val finishT = mock<SurfaceControl.Transaction>() transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT) } - companion object { - fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo { - val taskInfo = ActivityManager.RunningTaskInfo() - taskInfo.taskId = taskId - taskInfo.configuration.windowConfiguration.windowingMode = windowMode + private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) { + val sessionId = transitionObserver.getLoggerSessionId() + assertNotNull(sessionId) + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), eq(enterReason)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), eq(taskUpdate)) + verifyZeroInteractions(desktopModeEventLogger) + } - return taskInfo - } + private fun verifyTaskRemovedAndExitLogging( + sessionId: Int, + exitReason: ExitReason, + taskUpdate: TaskUpdate + ) { + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), eq(taskUpdate)) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), eq(exitReason)) + verifyZeroInteractions(desktopModeEventLogger) + assertNull(transitionObserver.getLoggerSessionId()) + } + + private companion object { + const val DEFAULT_TASK_ID = 1 + const val DEFAULT_TASK_UID = 2 + const val DEFAULT_TASK_HEIGHT = 100 + const val DEFAULT_TASK_WIDTH = 200 + const val DEFAULT_TASK_X = 30 + const val DEFAULT_TASK_Y = 70 + val DEFAULT_TASK_UPDATE = + TaskUpdate( + DEFAULT_TASK_ID, + DEFAULT_TASK_UID, + DEFAULT_TASK_HEIGHT, + DEFAULT_TASK_WIDTH, + DEFAULT_TASK_X, + DEFAULT_TASK_Y, + ) + + fun createTaskInfo( + windowMode: Int, + id: Int = DEFAULT_TASK_ID, + uid: Int = DEFAULT_TASK_UID, + taskHeight: Int = DEFAULT_TASK_HEIGHT, + taskWidth: Int = DEFAULT_TASK_WIDTH, + taskX: Int = DEFAULT_TASK_X, + taskY: Int = DEFAULT_TASK_Y, + ) = + ActivityManager.RunningTaskInfo().apply { + taskId = id + userId = uid + configuration.windowConfiguration.apply { + windowingMode = windowMode + positionInParent = Point(taskX, taskY) + bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight)) + } + } fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change { val change = - Change( - WindowContainerToken(mock(IWindowContainerToken::class.java)), - mock(SurfaceControl::class.java)) + Change(WindowContainerToken(mock<IWindowContainerToken>()), mock<SurfaceControl>()) change.mode = mode change.taskInfo = taskInfo return change diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 310ccc252469..6612aee0cd12 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -119,54 +119,91 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun isOnlyActiveTask_noActiveTasks() { - // Not an active task - assertThat(repo.isOnlyActiveTask(1)).isFalse() + fun isOnlyVisibleNonClosingTask_noTasks() { + // No visible tasks + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + assertThat(repo.isClosingTask(1)).isFalse() } @Test - fun isOnlyActiveTask_singleActiveTask() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - // The only active task - assertThat(repo.isActiveTask(1)).isTrue() - assertThat(repo.isOnlyActiveTask(1)).isTrue() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveTask(99)).isFalse() + fun isOnlyVisibleNonClosingTask_singleVisibleNonClosingTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + + // The only visible task + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isClosingTask(1)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isTrue() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isClosingTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() + } + + @Test + fun isOnlyVisibleNonClosingTask_singleVisibleClosingTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.addClosingTask(DEFAULT_DISPLAY, 1) + + // A visible task that's closing + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isClosingTask(1)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() + } + + @Test + fun isOnlyVisibleNonClosingTask_singleVisibleMinimizedTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.minimizeTask(DEFAULT_DISPLAY, 1) + + // The visible task that's closing + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isMinimizedTask(1)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test - fun isOnlyActiveTask_multipleActiveTasks() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - repo.addActiveTask(DEFAULT_DISPLAY, 2) + fun isOnlyVisibleNonClosingTask_multipleVisibleNonClosingTasks() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + // Not the only task - assertThat(repo.isActiveTask(1)).isTrue() - assertThat(repo.isOnlyActiveTask(1)).isFalse() + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isClosingTask(1)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() // Not the only task - assertThat(repo.isActiveTask(2)).isTrue() - assertThat(repo.isOnlyActiveTask(2)).isFalse() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveTask(99)).isFalse() + assertThat(repo.isVisibleTask(2)).isTrue() + assertThat(repo.isClosingTask(2)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isClosingTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test - fun isOnlyActiveTask_multipleDisplays() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - repo.addActiveTask(DEFAULT_DISPLAY, 2) - repo.addActiveTask(SECOND_DISPLAY, 3) + fun isOnlyVisibleNonClosingTask_multipleDisplays() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 3, visible = true) + // Not the only task on DEFAULT_DISPLAY - assertThat(repo.isActiveTask(1)).isTrue() - assertThat(repo.isOnlyActiveTask(1)).isFalse() + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() // Not the only task on DEFAULT_DISPLAY - assertThat(repo.isActiveTask(2)).isTrue() - assertThat(repo.isOnlyActiveTask(2)).isFalse() - // The only active task on SECOND_DISPLAY - assertThat(repo.isActiveTask(3)).isTrue() - assertThat(repo.isOnlyActiveTask(3)).isTrue() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveTask(99)).isFalse() + assertThat(repo.isVisibleTask(2)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse() + // The only visible task on SECOND_DISPLAY + assertThat(repo.isVisibleTask(3)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(3)).isTrue() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index e17f7f2f7b12..a1a18a9b7c9d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -45,6 +45,7 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE +import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT @@ -102,6 +103,8 @@ import com.google.common.truth.Truth.assertWithMessage import java.util.Optional import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import kotlin.test.assertNotNull +import kotlin.test.assertNull import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before @@ -923,7 +926,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun onDesktopWindowClose_noActiveTasks() { val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, 1 /* taskId */) + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = 1) // Doesn't modify transaction assertThat(wct.hierarchyOps).isEmpty() } @@ -932,7 +935,7 @@ class DesktopTasksControllerTest : ShellTestCase() { fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() { val task = setUpFreeformTask() val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, task.taskId) + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId) // Doesn't modify transaction assertThat(wct.hierarchyOps).isEmpty() } @@ -944,12 +947,38 @@ class DesktopTasksControllerTest : ShellTestCase() { desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, task.taskId) + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId) // Adds remove wallpaper operation wct.assertRemoveAt(index = 0, wallpaperToken) } @Test + fun onDesktopWindowClose_singleActiveTask_isClosing() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowClose_singleActiveTask_isMinimized() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test fun onDesktopWindowClose_multipleActiveTasks() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -957,12 +986,40 @@ class DesktopTasksControllerTest : ShellTestCase() { desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, task1.taskId) + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId) // Doesn't modify transaction assertThat(wct.hierarchyOps).isEmpty() } @Test + fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -1200,48 +1257,205 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_noToken() { + fun handleRequest_backTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() { val task = setUpFreeformTask() + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + assertNull(result, "Should not handle request") } @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperDisabled() { + fun handleRequest_backTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() { val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleActiveTasksFlagDisabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperEnabled() { + fun handleRequest_backTransition_multipleActiveTasksFlagEnabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleActiveTasksSingleNonClosing_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() { val task = setUpFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - assertThat(result).isNotNull() - // Creates remove wallpaper transaction - result!!.assertRemoveAt(index = 0, wallpaperToken) + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_multipleActiveTasks() { + fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() { + val task = setUpFreeformTask() + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksFlagDisabled_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksFlagEnabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksSingleNonClosing_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) } @Test @@ -1615,6 +1829,7 @@ class DesktopTasksControllerTest : ShellTestCase() { task.topActivityInfo = activityInfo whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) desktopModeTaskRepository.addActiveTask(displayId, task.taskId) + desktopModeTaskRepository.updateVisibleFreeformTasks(displayId, task.taskId, visible = true) desktopModeTaskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId) runningTasks.add(task) return task diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index 5880ffb0dce2..72950a8dc139 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -88,8 +88,11 @@ public class PipAnimationControllerTest extends ShellTestCase { @Test public void getAnimator_withBounds_returnBoundsAnimator() { + final Rect baseValue = new Rect(0, 0, 100, 100); + final Rect startValue = new Rect(0, 0, 100, 100); + final Rect endValue1 = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null, + .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); assertEquals("Expect ANIM_TYPE_BOUNDS animation", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index e291c0e1a151..5c5a1a26f5d2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -399,7 +399,7 @@ public class RecentTasksControllerTest extends ShellTestCase { } @Test - public void testGetRecentTasks_proto2Enabled_ignoresMinimizedFreeformTasks() { + public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() { ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); @@ -415,8 +415,7 @@ public class RecentTasksControllerTest extends ShellTestCase { ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); - // 2 freeform tasks should be grouped into one, 1 task should be skipped, 3 total recents - // entries + // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries assertEquals(3, recentTasks.size()); GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); @@ -428,9 +427,10 @@ public class RecentTasksControllerTest extends ShellTestCase { assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); // Check freeform group entries - assertEquals(2, freeformGroup.getTaskInfoList().size()); + assertEquals(3, freeformGroup.getTaskInfoList().size()); assertEquals(t1, freeformGroup.getTaskInfoList().get(0)); - assertEquals(t5, freeformGroup.getTaskInfoList().get(1)); + assertEquals(t3, freeformGroup.getTaskInfoList().get(1)); + assertEquals(t5, freeformGroup.getTaskInfoList().get(2)); // Check single entries assertEquals(t2, singleGroup1.getTaskInfo1()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 964d86e8bd35..69a61eadf61d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -1192,7 +1192,8 @@ public class ShellTransitionTests extends ShellTestCase { mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class)); final RecentsTransitionHandler recentsHandler = new RecentsTransitionHandler(shellInit, transitions, - mock(RecentTasksController.class), mock(HomeTransitionObserver.class)); + mock(RecentTasksController.class), mock(HomeTransitionObserver.class), + () -> mock(SurfaceControl.Transaction.class)); transitions.replaceDefaultHandlerForTest(mDefaultHandler); shellInit.init(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt index f750e6b9a6fe..86aded76c0f3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt @@ -21,7 +21,9 @@ import android.content.res.Resources import android.graphics.PointF import android.graphics.Rect import android.os.IBinder +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display import android.window.WindowContainerToken @@ -36,6 +38,7 @@ import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -53,21 +56,32 @@ import org.mockito.MockitoAnnotations class DragPositioningCallbackUtilityTest { @Mock private lateinit var mockWindowDecoration: WindowDecoration<*> + @Mock private lateinit var taskToken: WindowContainerToken + @Mock private lateinit var taskBinder: IBinder + @Mock private lateinit var mockDisplayController: DisplayController + @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock private lateinit var mockDisplay: Display + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockResources: Resources + @JvmField + @Rule + val setFlagsRule = SetFlagsRule() + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -323,6 +337,49 @@ class DragPositioningCallbackUtilityTest { assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 50) } + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS) + fun testChangeBounds_windowSizeExceedsStableBounds_shouldBeAllowedToChangeBounds() { + val startingPoint = + PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(), + OFF_CENTER_STARTING_BOUNDS.bottom.toFloat()) + val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS) + // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach + // the disallowed drag area. + val offset = 5 + val newX = STABLE_BOUNDS.right.toFloat() - offset + val newY = STABLE_BOUNDS.bottom.toFloat() - offset + val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) + + DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, + repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta, + mockDisplayController, mockWindowDecoration) + assertThat(repositionTaskBounds.width()).isGreaterThan(STABLE_BOUNDS.right) + assertThat(repositionTaskBounds.height()).isGreaterThan(STABLE_BOUNDS.bottom) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS) + fun testChangeBoundsInDesktopMode_windowSizeExceedsStableBounds_shouldBeLimitedToDisplaySize() { + whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true) + val startingPoint = + PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(), + OFF_CENTER_STARTING_BOUNDS.bottom.toFloat()) + val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS) + // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach + // the disallowed drag area. + val offset = 5 + val newX = STABLE_BOUNDS.right.toFloat() - offset + val newY = STABLE_BOUNDS.bottom.toFloat() - offset + val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) + + DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, + repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta, + mockDisplayController, mockWindowDecoration) + assertThat(repositionTaskBounds.width()).isLessThan(STABLE_BOUNDS.right) + assertThat(repositionTaskBounds.height()).isLessThan(STABLE_BOUNDS.bottom) + } + private fun initializeTaskInfo(taskMinWidth: Int = MIN_WIDTH, taskMinHeight: Int = MIN_HEIGHT) { mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = TASK_ID @@ -347,6 +404,7 @@ class DragPositioningCallbackUtilityTest { private const val NAVBAR_HEIGHT = 50 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) private val STARTING_BOUNDS = Rect(0, 0, 100, 100) + private val OFF_CENTER_STARTING_BOUNDS = Rect(-100, -100, 10, 10) private val DISALLOWED_RESIZE_AREA = Rect( DISPLAY_BOUNDS.left, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 48ac1e5717aa..901ca90b573e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -17,6 +17,8 @@ package com.android.wm.shell.windowdecor import android.app.ActivityManager import android.app.WindowConfiguration +import android.content.Context +import android.content.res.Resources import android.graphics.Point import android.graphics.Rect import android.os.IBinder @@ -98,6 +100,10 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private lateinit var mockFinishCallback: TransitionFinishCallback @Mock private lateinit var mockTransitions: Transitions + @Mock + private lateinit var mockContext: Context + @Mock + private lateinit var mockResources: Resources private lateinit var taskPositioner: VeiledResizeTaskPositioner @@ -105,6 +111,9 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + mockDesktopWindowDecoration.mDisplay = mockDisplay + mockDesktopWindowDecoration.mDecorWindowContext = mockContext + whenever(mockContext.getResources()).thenReturn(mockResources) whenever(taskToken.asBinder()).thenReturn(taskBinder) whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 0d0af1110ca4..4d185c69c172 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -28,8 +28,8 @@ #include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h> #include <include/gpu/ganesh/vk/GrVkBackendSurface.h> #include <include/gpu/ganesh/vk/GrVkDirectContext.h> +#include <include/gpu/vk/VulkanBackendContext.h> #include <ui/FatVector.h> -#include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> #include <sstream> @@ -141,7 +141,8 @@ VulkanManager::~VulkanManager() { mPhysicalDeviceFeatures2 = {}; } -void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFeatures2& features) { +void VulkanManager::setupDevice(skgpu::VulkanExtensions& grExtensions, + VkPhysicalDeviceFeatures2& features) { VkResult err; constexpr VkApplicationInfo app_info = { @@ -506,7 +507,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options, return vkGetInstanceProcAddr(instance, proc_name); }; - GrVkBackendContext backendContext; + skgpu::VulkanBackendContext backendContext; backendContext.fInstance = mInstance; backendContext.fPhysicalDevice = mPhysicalDevice; backendContext.fDevice = mDevice; diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index b92ebb3cdf71..08f9d4253d7e 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -24,8 +24,7 @@ #include <SkSurface.h> #include <android-base/unique_fd.h> #include <utils/StrongPointer.h> -#include <vk/GrVkBackendContext.h> -#include <vk/GrVkExtensions.h> +#include <vk/VulkanExtensions.h> #include <vulkan/vulkan.h> // VK_ANDROID_frame_boundary is a bespoke extension defined by AGI @@ -127,7 +126,7 @@ private: // Sets up the VkInstance and VkDevice objects. Also fills out the passed in // VkPhysicalDeviceFeatures struct. - void setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&); + void setupDevice(skgpu::VulkanExtensions&, VkPhysicalDeviceFeatures2&); // simple wrapper class that exists only to initialize a pointer to NULL template <typename FNPTR_TYPE> @@ -206,7 +205,7 @@ private: BufferAge, }; SwapBehavior mSwapBehavior = SwapBehavior::Discard; - GrVkExtensions mExtensions; + skgpu::VulkanExtensions mExtensions; uint32_t mDriverVersion = 0; std::once_flag mInitFlag; diff --git a/lint-baseline.xml b/lint-baseline.xml index 660884a18010..0320aabd7199 100644 --- a/lint-baseline.xml +++ b/lint-baseline.xml @@ -562,4 +562,10707 @@ column="74"/> </issue> + <issue + id="FlaggedApi" + message="Method `getItemCount()` is a flagged API and should be inside an `if (Flags.collectionInfoItemCounts())` check (or annotate the surrounding method `writeToParcelNoRecycle` with `@FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) to transfer requirement to caller`)" + errorLine1=" parcel.writeInt(mCollectionInfo.getItemCount());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java" + line="4498" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getImportantForAccessibilityItemCount()` is a flagged API and should be inside an `if (Flags.collectionInfoItemCounts())` check (or annotate the surrounding method `writeToParcelNoRecycle` with `@FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) to transfer requirement to caller`)" + errorLine1=" parcel.writeInt(mCollectionInfo.getImportantForAccessibilityItemCount());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java" + line="4499" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isGranularScrollingSupported()` is a flagged API and should be inside an `if (Flags.granularScrolling())` check (or annotate the surrounding method `toString` with `@FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING) to transfer requirement to caller`)" + errorLine1=" builder.append("; granularScrollingSupported: ").append(isGranularScrollingSupported());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java" + line="5079" + column="65"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `UNDEFINED` is a flagged API and should be inside an `if (Flags.collectionInfoItemCounts())` check (or annotate the surrounding method `CollectionInfo` with `@FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) to transfer requirement to caller`)" + errorLine1=" mItemCount = UNDEFINED;" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java" + line="6211" + column="26"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `UNDEFINED` is a flagged API and should be inside an `if (Flags.collectionInfoItemCounts())` check (or annotate the surrounding method `CollectionInfo` with `@FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) to transfer requirement to caller`)" + errorLine1=" mImportantForAccessibilityItemCount = UNDEFINED;" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java" + line="6212" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `UNDEFINED` is a flagged API and should be inside an `if (Flags.collectionInfoItemCounts())` check (or annotate the surrounding method `clear` with `@FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) to transfer requirement to caller`)" + errorLine1=" mItemCount = UNDEFINED;" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java" + line="6319" + column="26"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `UNDEFINED` is a flagged API and should be inside an `if (Flags.collectionInfoItemCounts())` check (or annotate the surrounding method `clear` with `@FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) to transfer requirement to caller`)" + errorLine1=" mImportantForAccessibilityItemCount = UNDEFINED;" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java" + line="6320" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="800" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="811" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="811" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="811" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="819" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="819" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="819" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="819" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="819" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="819" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="819" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="827" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java" + line="827" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setObservedMotionEventSources()` is a flagged API and should be inside an `if (Flags.motionEventObserving())` check (or annotate the surrounding method `initFromParcel` with `@FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING) to transfer requirement to caller`)" + errorLine1=" setObservedMotionEventSources(parcel.readInt());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/accessibilityservice/AccessibilityServiceInfo.java" + line="1420" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_WINDOW_CONTROL` is a flagged API and should be inside an `if (Flags.addTypeWindowControl())` check (or annotate the surrounding method `typeToString` with `@FlaggedApi(Flags.FLAG_ADD_TYPE_WINDOW_CONTROL) to transfer requirement to caller`)" + errorLine1=" if (Flags.addTypeWindowControl() && type == TYPE_WINDOW_CONTROL) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/AccessibilityWindowInfo.java" + line="883" + column="53"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `requestPermissions()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `requestPermissions` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" requestPermissions(permissions, requestCode, getDeviceId());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/Activity.java" + line="5636" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `EXTRA_REQUEST_PERMISSIONS_DEVICE_ID` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `dispatchRequestPermissionsResult` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, Context.DEVICE_ID_DEFAULT" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/Activity.java" + line="9530" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onRequestPermissionsResult()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `dispatchRequestPermissionsResult` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" onRequestPermissionsResult(requestCode, permissions, grantResults, deviceId);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/Activity.java" + line="9532" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `uptimeNanos()` is a flagged API and should be inside an `if (Flags.adpfGpuReportActualWorkDuration())` check (or annotate the surrounding method `handleBindApplication` with `@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) to transfer requirement to caller`)" + errorLine1=" timestampApplicationOnCreateNs = SystemClock.uptimeNanos();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ActivityThread.java" + line="7503" + column="50"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `SeServiceManager()` is a flagged API and should be inside an `if (Flags.enableNfcMainline())` check (or annotate the surrounding method `initializeMainlineModules` with `@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) to transfer requirement to caller`)" + errorLine1=" SeFrameworkInitializer.setSeServiceManager(new SeServiceManager());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ActivityThread.java" + line="8725" + column="52"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setSeServiceManager()` is a flagged API and should be inside an `if (Flags.enableNfcMainline())` check (or annotate the surrounding method `initializeMainlineModules` with `@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) to transfer requirement to caller`)" + errorLine1=" SeFrameworkInitializer.setSeServiceManager(new SeServiceManager());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ActivityThread.java" + line="8725" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `ProfilingServiceManager()` is a flagged API and should be inside an `if (Flags.telemetryApisFrameworkInitialization())` check (or annotate the surrounding method `initializeMainlineModules` with `@FlaggedApi(Flags.FLAG_TELEMETRY_APIS_FRAMEWORK_INITIALIZATION) to transfer requirement to caller`)" + errorLine1=" ProfilingFrameworkInitializer.setProfilingServiceManager(new ProfilingServiceManager());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ActivityThread.java" + line="8727" + column="70"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `TvExtender()` is a flagged API and should be inside an `if (Flags.apiTvextender())` check (or annotate the surrounding method `createNotification` with `@FlaggedApi(Flags.FLAG_API_TVEXTENDER) to transfer requirement to caller`)" + errorLine1=" .extend(new Notification.TvExtender()" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/debug/AdbNotifications.java" + line="95" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setChannelId()` is a flagged API and should be inside an `if (Flags.apiTvextender())` check (or annotate the surrounding method `createNotification` with `@FlaggedApi(Flags.FLAG_API_TVEXTENDER) to transfer requirement to caller`)" + errorLine1=" .extend(new Notification.TvExtender()" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/debug/AdbNotifications.java" + line="95" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isMgf1DigestsSpecified()` is a flagged API and should be inside an `if (Flags.mgf1DigestSetterV2())` check (or annotate the surrounding method `initialize` with `@FlaggedApi(Flags.FLAG_MGF1_DIGEST_SETTER_V2) to transfer requirement to caller`)" + errorLine1=" if (spec.isMgf1DigestsSpecified()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java" + line="346" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getMgf1Digests()` is a flagged API and should be inside an `if (Flags.mgf1DigestSetterV2())` check (or annotate the surrounding method `initialize` with `@FlaggedApi(Flags.FLAG_MGF1_DIGEST_SETTER_V2) to transfer requirement to caller`)" + errorLine1=" Set<String> mgfDigests = spec.getMgf1Digests();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java" + line="349" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isMgf1DigestsSpecified()` is a flagged API and should be inside an `if (Flags.mgf1DigestSetterV2())` check (or annotate the surrounding method `setPrivateKeyEntry` with `@FlaggedApi(Flags.FLAG_MGF1_DIGEST_SETTER_V2) to transfer requirement to caller`)" + errorLine1=" if (spec.isMgf1DigestsSpecified()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java" + line="539" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getMgf1Digests()` is a flagged API and should be inside an `if (Flags.mgf1DigestSetterV2())` check (or annotate the surrounding method `setPrivateKeyEntry` with `@FlaggedApi(Flags.FLAG_MGF1_DIGEST_SETTER_V2) to transfer requirement to caller`)" + errorLine1=" for (String mgf1Digest : spec.getMgf1Digests()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java" + line="540" + column="50"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_RCS_STRING` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS);" + errorLine2=" ~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="491" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_RCS` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS);" + errorLine2=" ~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="491" + column="50"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_RCS_STRING` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING);" + errorLine2=" ~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="509" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_RCS` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING);" + errorLine2=" ~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="509" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `MTU_V4` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `makeApnSetting` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" int mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V4));" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1075" + column="83"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setAlwaysOn()` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `makeApnSetting` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" return new Builder()" + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1080" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `MTU_V6` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `makeApnSetting` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" .setMtuV6(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V6)))" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1126" + column="89"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `ALWAYS_ON` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `makeApnSetting` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" .setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1)" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1137" + column="82"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setAlwaysOn()` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `makeApnSetting` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" return new Builder()" + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1151" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `MTU_V4` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `toContentValues` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" apnValue.put(Telephony.Carriers.MTU_V4, mMtuV4);" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1500" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `MTU_V6` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `toContentValues` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" apnValue.put(Telephony.Carriers.MTU_V6, mMtuV6);" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1501" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `ALWAYS_ON` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `toContentValues` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn);" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1504" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setAlwaysOn()` is a flagged API and should be inside an `if (Flags.apnSettingFieldSupportFlag())` check (or annotate the surrounding method `readFromParcel` with `@FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) to transfer requirement to caller`)" + errorLine1=" return new Builder()" + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="1785" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_RCS` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `build` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0" + errorLine2=" ~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/ApnSetting.java" + line="2386" + column="76"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `OPSTR_ACCESS_RESTRICTED_SETTINGS` is a flagged API and should be inside an `if (Flags.enhancedConfirmationModeApisEnabled())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="2983" + column="62"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `OPSTR_CREATE_ACCESSIBILITY_OVERLAY` is a flagged API and should be inside an `if (Flags.createAccessibilityOverlayAppOpEnabled())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED) to transfer requirement to caller`)" + errorLine1=" OPSTR_CREATE_ACCESSIBILITY_OVERLAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="3050" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `OPSTR_MEDIA_ROUTING_CONTROL` is a flagged API and should be inside an `if (Flags.enablePrivilegedRoutingForMediaRoutingControl())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL) to transfer requirement to caller`)" + errorLine1=" new AppOpInfo.Builder(OP_MEDIA_ROUTING_CONTROL, OPSTR_MEDIA_ROUTING_CONTROL," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="3053" + column="57"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `OPSTR_ENABLE_MOBILE_DATA_BY_USER` is a flagged API and should be inside an `if (Flags.opEnableMobileDataByUser())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER) to transfer requirement to caller`)" + errorLine1=" new AppOpInfo.Builder(OP_ENABLE_MOBILE_DATA_BY_USER, OPSTR_ENABLE_MOBILE_DATA_BY_USER," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="3056" + column="62"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER` is a flagged API and should be inside an `if (Flags.rapidClearNotificationsByListenerAppOpEnabled())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED) to transfer requirement to caller`)" + errorLine1=" OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="3061" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `OPSTR_EMERGENCY_LOCATION` is a flagged API and should be inside an `if (Flags.locationBypass())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_LOCATION_BYPASS) to transfer requirement to caller`)" + errorLine1=" new AppOpInfo.Builder(OP_EMERGENCY_LOCATION, OPSTR_EMERGENCY_LOCATION, "EMERGENCY_LOCATION")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="3077" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS` is a flagged API and should be inside an `if (Flags.redactSensitiveNotificationsFromUntrustedListeners())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) to transfer requirement to caller`)" + errorLine1=" OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS, "RECEIVE_SENSITIVE_NOTIFICATIONS")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="3083" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `OpEventProxyInfo` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="3550" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `onOpChanged` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="7483" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `getPackagesForOps` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="7891" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `unsafeCheckOpRawNoThrow` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" attributionSource.getPackageName(), attributionSource.getDeviceId());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="8848" + column="53"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `noteOpNoThrow` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" attributionSource.getAttributionTag(), attributionSource.getDeviceId(), message);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="9034" + column="56"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `checkOpNoThrow` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" attributionSource.getDeviceId());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="9329" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `startOpNoThrow` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" attributionSource.getAttributionTag(), attributionSource.getDeviceId()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="9589" + column="56"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `finishOp` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" attributionSource.getDeviceId());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AppOpsManager.java" + line="9842" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `EXTRA_REQUEST_PERMISSIONS_DEVICE_ID` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `buildRequestPermissionsIntent` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" intent.putExtra(EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, mContext.getDeviceId());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ApplicationPackageManager.java" + line="965" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `APP_METADATA_SOURCE_UNKNOWN` is a flagged API and should be inside an `if (Flags.aslInApkAppMetadataSource())` check (or annotate the surrounding method `getAppMetadataSource` with `@FlaggedApi(Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE) to transfer requirement to caller`)" + errorLine1=" int source = PackageManager.APP_METADATA_SOURCE_UNKNOWN;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ApplicationPackageManager.java" + line="1283" + column="37"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `isArchived` is a flagged API and should be inside an `if (Flags.archiving())` check (or annotate the surrounding method `loadUnbadgedItemIcon` with `@FlaggedApi(Flags.FLAG_ARCHIVING) to transfer requirement to caller`)" + errorLine1=" if (itemInfo.isArchived) {" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ApplicationPackageManager.java" + line="3430" + column="26"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `ArchivedPackageInfo()` is a flagged API and should be inside an `if (Flags.archiving())` check (or annotate the surrounding method `getArchivedPackage` with `@FlaggedApi(Flags.FLAG_ARCHIVING) to transfer requirement to caller`)" + errorLine1=" return new ArchivedPackageInfo(parcel);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ApplicationPackageManager.java" + line="3995" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPendingCredentialRequest()` is a flagged API and should be inside an `if (Flags.autofillCredmanDevIntegration())` check (or annotate the surrounding method `dump` with `@FlaggedApi(Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) to transfer requirement to caller`)" + errorLine1=" GetCredentialRequest getCredentialRequest = node.getPendingCredentialRequest();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/assist/AssistStructure.java" + line="2657" + column="53"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `AttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken, null," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="112" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `AttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" this(uid, Process.INVALID_PID, packageName, attributionTag, token," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="126" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `AttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="133" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `AttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="142" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `AttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" this(current.getUid(), current.getPid(), current.getPackageName()," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="150" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `AttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" current.mAttributionSourceState.renouncedPermissions, current.getDeviceId(), next);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="152" + column="71"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `AttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, deviceId," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="159" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withNextAttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag()," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="212" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withNextAttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), next);" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="213" + column="75"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withPackageName` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag()," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="218" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withPackageName` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="219" + column="74"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withToken` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag()," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="224" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withToken` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" token, mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="225" + column="70"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withPid` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag()," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="235" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withPid` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="236" + column="75"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `AttributionSource()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `withDeviceId` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag()," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="241" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setDeviceId()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `myAttributionSource` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" return new AttributionSource.Builder(uid)" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="284" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isRegisteredAttributionSource()` is a flagged API and should be inside an `if (Flags.shouldRegisterAttributionSource())` check (or annotate the surrounding method `isTrusted` with `@FlaggedApi(Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE) to transfer requirement to caller`)" + errorLine1=" && context.getSystemService(PermissionManager.class)" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/AttributionSource.java" + line="487" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isResumed()` is a flagged API and should be inside an `if (Flags.enableNfcMainline())` check (or annotate the surrounding method `isDisablingEnterExitEventForAutofill` with `@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) to transfer requirement to caller`)" + errorLine1=" return mAutoFillIgnoreFirstResumePause || !mActivity.isResumed();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/autofill/AutofillClientController.java" + line="473" + column="52"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_NAME` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_NAME) != 0) {" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AutomaticZenRule.java" + line="582" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_INTERRUPTION_FILTER` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_INTERRUPTION_FILTER) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AutomaticZenRule.java" + line="585" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_ICON` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_ICON) != 0) {" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/AutomaticZenRule.java" + line="588" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getFrameTimeNanos()` is a flagged API and should be inside an `if (Flags.expectedPresentationTimeApi())` check (or annotate the surrounding method `run` with `@FlaggedApi(Flags.FLAG_EXPECTED_PRESENTATION_TIME_API) to transfer requirement to caller`)" + errorLine1=" doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/BatchedInputEventReceiver.java" + line="130" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `Gainmap()` is a flagged API and should be inside an `if (Flags.gainmapConstructorWithMetadata())` check (or annotate the surrounding method `createBitmap` with `@FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA) to transfer requirement to caller`)" + errorLine1=" bitmap.setGainmap(new Gainmap(source.getGainmap(), newMapContents));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Bitmap.java" + line="1030" + column="35"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `Gainmap()` is a flagged API and should be inside an `if (Flags.gainmapConstructorWithMetadata())` check (or annotate the surrounding method `setOverrideGainmap` with `@FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA) to transfer requirement to caller`)" + errorLine1=" mOverrideGainmap = new Gainmap(overrideGainmap, overrideGainmap.getGainmapContents());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/BitmapShader.java" + line="193" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `set()` is a flagged API and should be inside an `if (Flags.fixLineHeightForLocale())` check (or annotate the surrounding method `isBoring` with `@FlaggedApi(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) to transfer requirement to caller`)" + errorLine1=" fm.set(minimumFontMetrics);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/BoringLayout.java" + line="594" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `getLineMax` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" if (getUseBoundsForWidth()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/BoringLayout.java" + line="658" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `getLineWidth` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" if (getUseBoundsForWidth()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/BoringLayout.java" + line="667" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getShiftDrawingOffsetForStartOverhang()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `draw` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" if (getUseBoundsForWidth() && getShiftDrawingOffsetForStartOverhang()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/BoringLayout.java" + line="720" + column="43"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `draw` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" if (getUseBoundsForWidth() && getShiftDrawingOffsetForStartOverhang()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/BoringLayout.java" + line="720" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `createFromParcelBody()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" return SignalingDataRequest.createFromParcelBody(source);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/BroadcastInfoRequest.java" + line="89" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `createFromParcelBody()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" return SignalingDataResponse.createFromParcelBody(source);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/BroadcastInfoResponse.java" + line="75" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `BUGREPORT_MODE_ONBOARDING` is a flagged API and should be inside an `if (Flags.onboardingBugreportV2Enabled())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED) to transfer requirement to caller`)" + errorLine1=" public static final int BUGREPORT_MODE_MAX_VALUE = BUGREPORT_MODE_ONBOARDING;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/BugreportParams.java" + line="139" + column="56"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PROPERTY_IS_TRANSACTIONAL` is a flagged API and should be inside an `if (Flags.voipAppActionsSupport())` check (or annotate the surrounding method `propertiesToString` with `@FlaggedApi(Flags.FLAG_VOIP_APP_ACTIONS_SUPPORT) to transfer requirement to caller`)" + errorLine1=" if (hasProperty(properties, PROPERTY_IS_TRANSACTIONAL)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/Call.java" + line="854" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getImsReasonInfo()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `handleCallDisconnected` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" if (disconnectCause.getImsReasonInfo() != null) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/CallDiagnosticService.java" + line="365" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getImsReasonInfo()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `handleCallDisconnected` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" message = callDiagnostics.onCallDisconnected(disconnectCause.getImsReasonInfo());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/CallDiagnosticService.java" + line="366" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getTelephonyDisconnectCause()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `handleCallDisconnected` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" disconnectCause.getTelephonyDisconnectCause()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/CallDiagnosticService.java" + line="369" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getTelephonyPreciseDisconnectCause()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `handleCallDisconnected` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" disconnectCause.getTelephonyPreciseDisconnectCause());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/CallDiagnosticService.java" + line="370" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `POLICY_TYPE_CAMERA` is a flagged API and should be inside an `if (Flags.virtualCamera())` check (or annotate the surrounding method `getDevicePolicyFromContext` with `@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) to transfer requirement to caller`)" + errorLine1=" return virtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA);" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/Camera.java" + line="350" + column="76"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FALLBACK_PACKAGE_NAME` is a flagged API and should be inside an `if (Flags.concertMode())` check (or annotate the surrounding method `onSuccess` with `@FlaggedApi(Flags.FLAG_CONCERT_MODE) to transfer requirement to caller`)" + errorLine1=" .getString(FALLBACK_PACKAGE_NAME);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java" + line="591" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `POLICY_TYPE_CAMERA` is a flagged API and should be inside an `if (Flags.virtualCamera())` check (or annotate the surrounding method `getDevicePolicyFromContext` with `@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) to transfer requirement to caller`)" + errorLine1=" return mVirtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA);" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/camera2/CameraManager.java" + line="584" + column="77"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `CameraDeviceSetupImpl()` is a flagged API and should be inside an `if (Flags.cameraDeviceSetup())` check (or annotate the surrounding method `getCameraDeviceSetupUnsafe` with `@FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) to transfer requirement to caller`)" + errorLine1=" return new CameraDeviceSetupImpl(cameraId, /*cameraManager=*/ this, mContext);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/camera2/CameraManager.java" + line="903" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isCameraDeviceSetupSupported()` is a flagged API and should be inside an `if (Flags.cameraDeviceSetup())` check (or annotate the surrounding method `openCameraDeviceUserAsync` with `@FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) to transfer requirement to caller`)" + errorLine1=" && CameraDeviceSetupImpl.isCameraDeviceSetupSupported(characteristics)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/camera2/CameraManager.java" + line="983" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `DeviceStateManager` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `registerDeviceStateListener` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" ctx.getSystemService(DeviceStateManager.class).registerCallback(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/camera2/CameraManager.java" + line="2141" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `registerCallback()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `registerDeviceStateListener` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" ctx.getSystemService(DeviceStateManager.class).registerCallback(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/hardware/camera2/CameraManager.java" + line="2141" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `STATISTICS_LENS_INTRINSICS_SAMPLES` is a flagged API and should be inside an `if (Flags.concertMode())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CONCERT_MODE) to transfer requirement to caller`)" + errorLine1=" CaptureResult.STATISTICS_LENS_INTRINSICS_SAMPLES.getNativeKey()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/camera2/impl/CameraMetadataNative.java" + line="856" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `STATISTICS_LENS_INTRINSICS_SAMPLES` is a flagged API and should be inside an `if (Flags.concertMode())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CONCERT_MODE) to transfer requirement to caller`)" + errorLine1=" CaptureResult.STATISTICS_LENS_INTRINSICS_SAMPLES.getNativeKey()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/camera2/impl/CameraMetadataNative.java" + line="2021" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8054" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8060" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8066" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8073" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8079" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DOMAIN_PS_3GPP` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" DOMAIN_PS_3GPP," + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8081" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DOMAIN_CS` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" DOMAIN_CS," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8082" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DOMAIN_PS_NON_3GPP` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" DOMAIN_PS_NON_3GPP" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8083" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8085" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DOMAIN_PS_3GPP` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" DOMAIN_PS_3GPP," + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8087" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DOMAIN_CS` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" DOMAIN_CS," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8088" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DOMAIN_PS_NON_3GPP` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" DOMAIN_PS_NON_3GPP" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8089" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8092" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, VOWIFI_REQUIRES_NONE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8093" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `VOWIFI_REQUIRES_NONE` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, VOWIFI_REQUIRES_NONE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8093" + column="74"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 1);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8094" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_SCAN_TIMER_SEC_INT` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, 10);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8095" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8096" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REDIAL_TIMER_DISABLED` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8096" + column="72"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8097" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SCAN_TYPE_NO_PREFERENCE` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8097" + column="66"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT, 0);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8098" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8099" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putBoolean(KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8100" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8101" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8102" + column="37"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, 120);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8104" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8105" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REDIAL_TIMER_DISABLED` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8105" + column="73"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8106" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL` is a flagged API and should be inside an `if (Flags.useOemDomainSelectionService())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) to transfer requirement to caller`)" + errorLine1=" defaults.putBoolean(KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="8108" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL` is a flagged API and should be inside an `if (Flags.enableMultipleSaProposals())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS) to transfer requirement to caller`)" + errorLine1=" defaults.putBoolean(KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="9405" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL` is a flagged API and should be inside an `if (Flags.enableMultipleSaProposals())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS) to transfer requirement to caller`)" + errorLine1=" defaults.putBoolean(KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="9406" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY` is a flagged API and should be inside an `if (Flags.enableAeadAlgorithms())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) to transfer requirement to caller`)" + errorLine1=" KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {});" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="9422" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY` is a flagged API and should be inside an `if (Flags.enableAeadAlgorithms())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) to transfer requirement to caller`)" + errorLine1=" KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {});" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="9427" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY` is a flagged API and should be inside an `if (Flags.enableAeadAlgorithms())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) to transfer requirement to caller`)" + errorLine1=" KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {});" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="9477" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY` is a flagged API and should be inside an `if (Flags.enableAeadAlgorithms())` check (or annotate the surrounding method `getDefaults` with `@FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) to transfer requirement to caller`)" + errorLine1=" KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {});" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="9479" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL` is a flagged API and should be inside an `if (Flags.showCallIdAndCallWaitingInAdditionalSettingsMenu())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU) to transfer requirement to caller`)" + errorLine1=" sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL, true);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="10519" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL` is a flagged API and should be inside an `if (Flags.showCallIdAndCallWaitingInAdditionalSettingsMenu())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU) to transfer requirement to caller`)" + errorLine1=" sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL, true);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="10520" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_PREFER_3G_VISIBILITY_BOOL` is a flagged API and should be inside an `if (Flags.hidePrefer3gItem())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HIDE_PREFER_3G_ITEM) to transfer requirement to caller`)" + errorLine1=" sDefaults.putBoolean(KEY_PREFER_3G_VISIBILITY_BOOL, true);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="10526" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SHOW_ROAMING_INDICATOR_BOOL` is a flagged API and should be inside an `if (Flags.hideRoamingIcon())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HIDE_ROAMING_ICON) to transfer requirement to caller`)" + errorLine1=" sDefaults.putBoolean(KEY_SHOW_ROAMING_INDICATOR_BOOL, true);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="10794" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11093" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SATELLITE_ATTACH_SUPPORTED_BOOL` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11095" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" sDefaults.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 300);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11096" + column="26"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" sDefaults.putIntArray(KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11097" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" sDefaults.putIntArray(KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11105" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" sDefaults.putIntArray(KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11113" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11121" + column="26"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 7);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11127" + column="26"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11128" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SERVICE_TYPE_MMS` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" NetworkRegistrationInfo.SERVICE_TYPE_MMS" + errorLine2=" ~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11134" + column="49"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL` is a flagged API and should be inside an `if (Flags.businessCallComposer())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER) to transfer requirement to caller`)" + errorLine1=" sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11142" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY` is a flagged API and should be inside an `if (Flags.dataOnlyCellularService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) to transfer requirement to caller`)" + errorLine1=" sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11233" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `TelephonyRegistryManager` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `registerCarrierConfigChangeListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11771" + column="66"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `addCarrierConfigChangedListener()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `registerCarrierConfigChangeListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" trm.addCarrierConfigChangedListener(executor, listener);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11775" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `TelephonyRegistryManager` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `unregisterCarrierConfigChangeListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11789" + column="66"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `removeCarrierConfigChangedListener()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `unregisterCarrierConfigChangeListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" trm.removeCarrierConfigChangedListener(listener);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java" + line="11793" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `notifyCarrierNetworkChange` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" (TelephonyRegistryManager) this.getSystemService(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/service/carrier/CarrierService.java" + line="180" + column="14"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `notifyCarrierNetworkChange()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `notifyCarrierNetworkChange` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" telephonyRegistryMgr.notifyCarrierNetworkChange(active);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/service/carrier/CarrierService.java" + line="183" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `TelephonyRegistryManager` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `notifyCarrierNetworkChange` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" TelephonyRegistryManager.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/service/carrier/CarrierService.java" + line="206" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `notifyCarrierNetworkChange()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `notifyCarrierNetworkChange` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" telephonyRegistryMgr.notifyCarrierNetworkChange(subscriptionId, active);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/service/carrier/CarrierService.java" + line="208" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isUsingNonTerrestrialNetwork()` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `updateLevel` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" if (ss != null && ss.isUsingNonTerrestrialNetwork()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CellSignalStrengthLte.java" + line="266" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `updateLevel` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" CarrierConfigManager.KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CellSignalStrengthLte.java" + line="269" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `updateLevel` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CellSignalStrengthLte.java" + line="271" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `updateLevel` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CellSignalStrengthLte.java" + line="273" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `updateLevel` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" CarrierConfigManager.KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/CellSignalStrengthLte.java" + line="275" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getFrameTimeNanos()` is a flagged API and should be inside an `if (Flags.expectedPresentationTimeApi())` check (or annotate the surrounding method `getFrameTime` with `@FlaggedApi(Flags.FLAG_EXPECTED_PRESENTATION_TIME_API) to transfer requirement to caller`)" + errorLine1=" return getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/Choreographer.java" + line="694" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `lockAnimationClock()` is a flagged API and should be inside an `if (Flags.expectedPresentationTimeReadOnly())` check (or annotate the surrounding method `doFrame` with `@FlaggedApi(Flags.FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY) to transfer requirement to caller`)" + errorLine1=" AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/Choreographer.java" + line="934" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SOURCE_UNKNOWN` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `Condition` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" this(id, summary, "", "", -1, state, SOURCE_UNKNOWN, FLAG_RELEVANT_ALWAYS);" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/Condition.java" + line="138" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `Condition()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `Condition` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" this(id, summary, "", "", -1, state, SOURCE_UNKNOWN, FLAG_RELEVANT_ALWAYS);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/Condition.java" + line="138" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SOURCE_UNKNOWN` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `Condition` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" this(id, summary, line1, line2, icon, state, SOURCE_UNKNOWN, flags);" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/Condition.java" + line="157" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `Condition()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `Condition` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" this(id, summary, line1, line2, icon, state, SOURCE_UNKNOWN, flags);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/Condition.java" + line="157" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `Condition()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `Condition` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class)," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/service/notification/Condition.java" + line="192" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SOURCE_UNKNOWN` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `Condition` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" Flags.modesApi() ? source.readInt() : SOURCE_UNKNOWN," + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/Condition.java" + line="198" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onCreateConnectionComplete()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `notifyCreateConnectionComplete` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" onCreateConnectionComplete(findConnectionForAction(callId," + errorLine2=" ^"> + <location + file="frameworks/base/telecomm/java/android/telecom/ConnectionService.java" + line="2531" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onCreateConferenceComplete()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `notifyCreateConferenceComplete` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" onCreateConferenceComplete(findConferenceForAction(callId," + errorLine2=" ^"> + <location + file="frameworks/base/telecomm/java/android/telecom/ConnectionService.java" + line="2548" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getCallDirection()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `addExistingConnection` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" connection.getCallDirection()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/ConnectionService.java" + line="3179" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `supportsReliableMessages()` is a flagged API and should be inside an `if (Flags.reliableMessage())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE) to transfer requirement to caller`)" + errorLine1=" || (other.supportsReliableMessages() == mSupportsReliableMessages))" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/location/ContextHubInfo.java" + line="365" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isReliable()` is a flagged API and should be inside an `if (Flags.reliableMessage())` check (or annotate the surrounding method `onMessageFromNanoApp` with `@FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE) to transfer requirement to caller`)" + errorLine1=" && message.isReliable()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/location/ContextHubManager.java" + line="715" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_RELIABLE_MESSAGE` is a flagged API and should be inside an `if (Flags.reliableMessage())` check (or annotate the surrounding method `typeToString` with `@FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE) to transfer requirement to caller`)" + errorLine1=" case ContextHubTransaction.TYPE_RELIABLE_MESSAGE: {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/location/ContextHubTransaction.java" + line="235" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `shouldRegisterAttributionSource()` is a flagged API and should be inside an `if (Flags.shouldRegisterAttributionSource())` check (or annotate the surrounding method `ContextImpl` with `@FlaggedApi(Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE) to transfer requirement to caller`)" + errorLine1=" params.getRenouncedPermissions(), params.shouldRegisterAttributionSource(), mDeviceId);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ContextImpl.java" + line="3540" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_UNSUPPORTED` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `DataCallResponse` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/DataCallResponse.java" + line="192" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_UNSUPPORTED` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/DataCallResponse.java" + line="655" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_RCS` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `networkCapabilityToApnType` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" return ApnSetting.TYPE_RCS;" + errorLine2=" ~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/data/DataProfile.java" + line="435" + column="35"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `requestNetworkValidation()` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" serviceProvider.requestNetworkValidation(" + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/data/DataService.java" + line="744" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER` is a flagged API and should be inside an `if (Flags.headlessDeviceOwnerSingleUserEnabled())` check (or annotate the surrounding method `DeviceAdminInfo` with `@FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED) to transfer requirement to caller`)" + errorLine1=" mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/admin/DeviceAdminInfo.java" + line="410" + column="52"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTENT_PROTECTION_DISABLED` is a flagged API and should be inside an `if (Flags.manageDevicePolicyEnabled())` check (or annotate the surrounding method `getContentProtectionPolicy` with `@FlaggedApi(Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) to transfer requirement to caller`)" + errorLine1=" return CONTENT_PROTECTION_DISABLED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/admin/DevicePolicyCache.java" + line="105" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `equals()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" return baseState.equals(that.baseState)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="101" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `equals()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" && currentState.equals(that.currentState)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="102" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `equals()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `diff` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" if (!baseState.equals(other.baseState)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="120" + column="14"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `equals()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `diff` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" if (!currentState.equals(other.currentState)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="123" + column="14"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getConfiguration()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `writeToParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" dest.writeTypedObject(supportedStates.get(i).getConfiguration(), flags);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="133" + column="35"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getConfiguration()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `writeToParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" dest.writeTypedObject(baseState.getConfiguration(), flags);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="136" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getConfiguration()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `writeToParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" dest.writeTypedObject(currentState.getConfiguration(), flags);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="137" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CREATOR` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" DeviceState.Configuration.CREATOR);" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="152" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `DeviceState()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" supportedStates.add(i, new DeviceState(configuration));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="153" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `DeviceState()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" final DeviceState baseState = new DeviceState(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="156" + column="43"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CREATOR` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" source.readTypedObject(DeviceState.Configuration.CREATOR));" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="157" + column="70"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `DeviceState()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" final DeviceState currentState = new DeviceState(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="158" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CREATOR` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" source.readTypedObject(DeviceState.Configuration.CREATOR));" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateInfo.java" + line="159" + column="70"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onSupportedStatesChanged()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `notifySupportedDeviceStatesChanged` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java" + line="393" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onDeviceStateChanged()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `notifyDeviceStateChanged` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" () -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java" + line="398" + column="27"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getConfiguration()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `calculateBaseStateIdentifier` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" DeviceState.Configuration stateConfiguration = currentState.getConfiguration();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateUtil.java" + line="45" + column="56"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getConfiguration()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `calculateBaseStateIdentifier` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" if (stateToCompare.getConfiguration().getPhysicalProperties().isEmpty()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateUtil.java" + line="48" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPhysicalProperties()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `calculateBaseStateIdentifier` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" if (stateToCompare.getConfiguration().getPhysicalProperties().isEmpty()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateUtil.java" + line="48" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPhysicalProperties()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `calculateBaseStateIdentifier` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" if (isDeviceStateMatchingPhysicalProperties(stateConfiguration.getPhysicalProperties()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateUtil.java" + line="51" + column="57"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getIdentifier()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `calculateBaseStateIdentifier` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" return supportedStates.get(i).getIdentifier();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateUtil.java" + line="53" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `INVALID_DEVICE_STATE_IDENTIFIER` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `calculateBaseStateIdentifier` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" return INVALID_DEVICE_STATE_IDENTIFIER;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateUtil.java" + line="56" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `hasProperty()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `isDeviceStateMatchingPhysicalProperties` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" if (!state.hasProperty(iterator.next())) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/devicestate/DeviceStateUtil.java" + line="69" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getTelephonyDisconnectCause()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" && Objects.equals(mTelephonyDisconnectCause, d.getTelephonyDisconnectCause())" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/DisconnectCause.java" + line="498" + column="66"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getTelephonyPreciseDisconnectCause()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" d.getTelephonyPreciseDisconnectCause())" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/DisconnectCause.java" + line="500" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getImsReasonInfo()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" && Objects.equals(mImsReasonInfo, d.getImsReasonInfo());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/DisconnectCause.java" + line="501" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SATELLITE_ENABLED` is a flagged API and should be inside an `if (Flags.oemEnabledSatelliteFlag())` check (or annotate the surrounding method `toString` with `@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" case SATELLITE_ENABLED:" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/DisconnectCause.java" + line="550" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `dreamCategory` is a flagged API and should be inside an `if (Flags.homePanelDream())` check (or annotate the surrounding method `DreamMetadata` with `@FlaggedApi(Flags.FLAG_HOME_PANEL_DREAM) to transfer requirement to caller`)" + errorLine1=" this.dreamCategory = DREAM_CATEGORY_DEFAULT;" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/dreams/DreamService.java" + line="1825" + column="22"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setMinimumFontMetrics()` is a flagged API and should be inside an `if (Flags.fixLineHeightForLocale())` check (or annotate the surrounding method `reflow` with `@FlaggedApi(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) to transfer requirement to caller`)" + errorLine1=" b.setText(text, where, where + after)" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/text/DynamicLayout.java" + line="736" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setShiftDrawingOffsetForStartOverhang()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `reflow` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" b.setText(text, where, where + after)" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/text/DynamicLayout.java" + line="736" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `reflow` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" b.setText(text, where, where + after)" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/text/DynamicLayout.java" + line="736" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getTextDirectionHeuristic()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `reflow` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" .setTextDirection(getTextDirectionHeuristic())" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/DynamicLayout.java" + line="739" + column="35"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setLocalePreferredLineHeightForMinimumUsed()` is a flagged API and should be inside an `if (Flags.fixLineHeightForLocale())` check (or annotate the surrounding method `EditText` with `@FlaggedApi(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) to transfer requirement to caller`)" + errorLine1=" setLocalePreferredLineHeightForMinimumUsed(useLocalePreferredLineHeightForMinimumInt);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/widget/EditText.java" + line="150" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `convertSpToDp()` is a flagged API and should be inside an `if (Flags.fontScaleConverterPublic())` check (or annotate the surrounding method `createInterpolatedTableBetween` with `@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) to transfer requirement to caller`)" + errorLine1=" float startDp = start.convertSpToDp(sp);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java" + line="242" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `convertSpToDp()` is a flagged API and should be inside an `if (Flags.fontScaleConverterPublic())` check (or annotate the surrounding method `createInterpolatedTableBetween` with `@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) to transfer requirement to caller`)" + errorLine1=" float endDp = end.convertSpToDp(sp);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java" + line="243" + column="27"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.fontScaleConverterPublic())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) to transfer requirement to caller`)" + errorLine1="public class FontScaleConverterImpl implements FontScaleConverter {" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/FontScaleConverterImpl.java" + line="36" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING` is a flagged API and should be inside an `if (Flags.introduceMediaProcessingType())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_INTRODUCE_MEDIA_PROCESSING_TYPE) to transfer requirement to caller`)" + errorLine1=" FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ForegroundServiceTypePolicy.java" + line="587" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING` is a flagged API and should be inside an `if (Flags.introduceMediaProcessingType())` check (or annotate the surrounding method `DefaultForegroundServiceTypePolicy` with `@FlaggedApi(Flags.FLAG_INTRODUCE_MEDIA_PROCESSING_TYPE) to transfer requirement to caller`)" + errorLine1=" mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/ForegroundServiceTypePolicy.java" + line="1355" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `InputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `doCreate` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" new InputTransferToken(), "GameSessionService");" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/games/GameSessionService.java" + line="127" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CREATOR` is a flagged API and should be inside an `if (Flags.configurableSelectorUiEnabled())` check (or annotate the surrounding method `GetCandidateCredentialsResponse` with `@FlaggedApi(Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED) to transfer requirement to caller`)" + errorLine1=" in.readTypedList(candidateProviderDataList, GetCredentialProviderData.CREATOR);" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/core/java/android/credentials/GetCandidateCredentialsResponse.java" + line="98" + column="79"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isConnectionlessStylusHandwritingAvailable()` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `prepareDelegation` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" if (mImm.isConnectionlessStylusHandwritingAvailable()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/HandwritingInitiator.java" + line="450" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `startConnectionlessStylusHandwritingForDelegation()` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `prepareDelegation` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" mImm.startConnectionlessStylusHandwritingForDelegation(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/HandwritingInitiator.java" + line="454" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" private class DelegationCallback implements ConnectionlessHandwritingCallback {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/HandwritingInitiator.java" + line="1116" + column="49"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `onError` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" case CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/HandwritingInitiator.java" + line="1133" + column="22"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `onError` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" case CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/HandwritingInitiator.java" + line="1136" + column="22"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.scrollFeedbackApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API) to transfer requirement to caller`)" + errorLine1="public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/HapticScrollFeedbackProvider.java" + line="36" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getOverlaySupport()` is a flagged API and should be inside an `if (Flags.overlaypropertiesClassApi())` check (or annotate the surrounding method `initDisplayInfo` with `@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API) to transfer requirement to caller`)" + errorLine1=" final OverlayProperties overlayProperties = defaultDisplay.getOverlaySupport();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/HardwareRenderer.java" + line="1394" + column="57"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isCombinationSupported()` is a flagged API and should be inside an `if (Flags.overlaypropertiesClassApi())` check (or annotate the surrounding method `initDisplayInfo` with `@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API) to transfer requirement to caller`)" + errorLine1=" overlayProperties.isCombinationSupported(" + errorLine2=" ^"> + <location + file="frameworks/base/graphics/java/android/graphics/HardwareRenderer.java" + line="1422" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isCombinationSupported()` is a flagged API and should be inside an `if (Flags.overlaypropertiesClassApi())` check (or annotate the surrounding method `initDisplayInfo` with `@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API) to transfer requirement to caller`)" + errorLine1=" overlayProperties.isCombinationSupported(" + errorLine2=" ^"> + <location + file="frameworks/base/graphics/java/android/graphics/HardwareRenderer.java" + line="1424" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `RGBA_10101010` is a flagged API and should be inside an `if (Flags.requestedFormatsV())` check (or annotate the surrounding method `initDisplayInfo` with `@FlaggedApi(Flags.FLAG_REQUESTED_FORMATS_V) to transfer requirement to caller`)" + errorLine1=" HardwareBuffer.RGBA_10101010)," + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/HardwareRenderer.java" + line="1429" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isMixedColorSpacesSupported()` is a flagged API and should be inside an `if (Flags.overlaypropertiesClassApi())` check (or annotate the surrounding method `initDisplayInfo` with `@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API) to transfer requirement to caller`)" + errorLine1=" overlayProperties.isMixedColorSpacesSupported());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/HardwareRenderer.java" + line="1430" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONFIDENCE_LEVEL_VERY_HIGH` is a flagged API and should be inside an `if (Flags.allowHotwordBumpEgress())` check (or annotate the surrounding method `confidenceLevelToString` with `@FlaggedApi(Flags.FLAG_ALLOW_HOTWORD_BUMP_EGRESS) to transfer requirement to caller`)" + errorLine1=" case CONFIDENCE_LEVEL_VERY_HIGH:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/voice/HotwordRejectedResult.java" + line="120" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `scheduleMediaViewCleanup()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `release` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.scheduleMediaViewCleanup();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="87" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `release()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.release();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="101" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setSurface()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.setSurface((Surface) msg.obj);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="114" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `dispatchSurfaceChanged()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.dispatchSurfaceChanged(" + errorLine2=" ^"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="119" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `createMediaView()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.createMediaView((IBinder) args.arg1, (Rect) args.arg2);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="126" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `relayoutMediaView()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.relayoutMediaView((Rect) msg.obj);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="131" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `removeMediaView()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.removeMediaView(true);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="135" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `startAdService()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.startAdService();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="139" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `stopAdService()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.stopAdService();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="143" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `resetAdService()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.resetAdService();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="147" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `sendCurrentVideoBounds()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.sendCurrentVideoBounds((Rect) msg.obj);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="151" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `sendCurrentChannelUri()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.sendCurrentChannelUri((Uri) msg.obj);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="155" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `sendTrackInfoList()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.sendTrackInfoList((List<TvTrackInfo>) msg.obj);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="159" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `sendCurrentTvInputId()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.sendCurrentTvInputId((String) msg.obj);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="163" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `sendSigningResult()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="168" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `notifyError()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.notifyError((String) args.arg1, (Bundle) args.arg2);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="174" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `notifyTvMessage()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.notifyTvMessage((Integer) args.arg1, (Bundle) args.arg2);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="180" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `notifyTvInputSessionData()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `executeMessage` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSessionImpl.notifyTvInputSessionData((String) args.arg1, (Bundle) args.arg2);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="186" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `dispatchInputEvent()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `onInputEvent` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" int handled = mSessionImpl.dispatchInputEvent(event, this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="309" + column="27"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DISPATCH_IN_PROGRESS` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `onInputEvent` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" if (handled != TvAdManager.Session.DISPATCH_IN_PROGRESS) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="310" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DISPATCH_HANDLED` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `onInputEvent` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" event, handled == TvAdManager.Session.DISPATCH_HANDLED);" + errorLine2=" ~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/ad/ITvAdSessionWrapper.java" + line="312" + column="63"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onDeregistered()` is a flagged API and should be inside an `if (Flags.emergencyRegistrationState())` check (or annotate the surrounding method `onDeregistered` with `@FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE) to transfer requirement to caller`)" + errorLine1=" onDeregistered(info, suggestedAction, attributes);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java" + line="576" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onTechnologyChangeFailed()` is a flagged API and should be inside an `if (Flags.emergencyRegistrationState())` check (or annotate the surrounding method `onTechnologyChangeFailed` with `@FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE) to transfer requirement to caller`)" + errorLine1=" onTechnologyChangeFailed(info, attributes);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java" + line="703" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `ATTR_REGISTRATION_TYPE_EMERGENCY` is a flagged API and should be inside an `if (Flags.emergencyRegistrationState())` check (or annotate the surrounding method `isEmergency` with `@FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE) to transfer requirement to caller`)" + errorLine1=" & ImsRegistrationAttributes.ATTR_REGISTRATION_TYPE_EMERGENCY) != 0;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java" + line="753" + column="49"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING` is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" Long.numberOfTrailingZeros(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/ims/ImsService.java" + line="186" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING` is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING, "SIMULTANEOUS_CALLING");" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/ims/ImsService.java" + line="212" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `InputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `handleRenderSuggestion` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" new InputTransferToken(hostInputToken), "InlineSuggestionRenderService");" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/autofill/InlineSuggestionRenderService.java" + line="170" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `ViewBehavior()` is a flagged API and should be inside an `if (Flags.inputDeviceViewBehaviorApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API) to transfer requirement to caller`)" + errorLine1=" private final ViewBehavior mViewBehavior = new ViewBehavior(this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/InputDevice.java" + line="99" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `mShouldSmoothScroll` is a flagged API and should be inside an `if (Flags.inputDeviceViewBehaviorApi())` check (or annotate the surrounding method `InputDevice` with `@FlaggedApi(Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API) to transfer requirement to caller`)" + errorLine1=" mViewBehavior.mShouldSmoothScroll = in.readBoolean();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/InputDevice.java" + line="575" + column="23"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `mShouldSmoothScroll` is a flagged API and should be inside an `if (Flags.inputDeviceViewBehaviorApi())` check (or annotate the surrounding method `setShouldSmoothScroll` with `@FlaggedApi(Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API) to transfer requirement to caller`)" + errorLine1=" mViewBehavior.mShouldSmoothScroll = shouldSmoothScroll;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/InputDevice.java" + line="1207" + column="23"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `mShouldSmoothScroll` is a flagged API and should be inside an `if (Flags.inputDeviceViewBehaviorApi())` check (or annotate the surrounding method `writeToParcel` with `@FlaggedApi(Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API) to transfer requirement to caller`)" + errorLine1=" out.writeBoolean(mViewBehavior.mShouldSmoothScroll);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/InputDevice.java" + line="1642" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getHandwritingDelegateFlags()` is a flagged API and should be inside an `if (Flags.homeScreenHandwritingDelegator())` check (or annotate the surrounding method `acceptStylusHandwritingDelegation` with `@FlaggedApi(Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR) to transfer requirement to caller`)" + errorLine1=" delegateView.getHandwritingDelegateFlags());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java" + line="2880" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getHandwritingDelegateFlags()` is a flagged API and should be inside an `if (Flags.homeScreenHandwritingDelegator())` check (or annotate the surrounding method `acceptStylusHandwritingDelegation` with `@FlaggedApi(Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR) to transfer requirement to caller`)" + errorLine1=" delegateView, delegatorPackageName, delegateView.getHandwritingDelegateFlags());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java" + line="2911" + column="53"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `acceptStylusHandwritingDelegation()` is a flagged API and should be inside an `if (Flags.homeScreenHandwritingDelegator())` check (or annotate the surrounding method `acceptStylusHandwritingDelegation` with `@FlaggedApi(Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR) to transfer requirement to caller`)" + errorLine1=" acceptStylusHandwritingDelegation(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java" + line="2940" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onError()` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `onResult` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" executor.execute(() -> callback.onError(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java" + line="4808" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `onResult` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" .CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java" + line="4810" + column="38"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onResult()` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `onResult` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" executor.execute(() -> callback.onResult(text));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java" + line="4812" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onError()` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `onError` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" executor.execute(() -> callback.onError(errorCode));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java" + line="4834" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onStartConnectionlessStylusHandwriting()` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `canStartStylusHandwriting` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" if (onStartConnectionlessStylusHandwriting(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/inputmethodservice/InputMethodService.java" + line="1085" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `canStartStylusHandwriting` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/inputmethodservice/InputMethodService.java" + line="1096" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONNECTIONLESS_HANDWRITING_ERROR_OTHER` is a flagged API and should be inside an `if (Flags.connectionlessHandwriting())` check (or annotate the surrounding method `finishStylusHandwriting` with `@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) to transfer requirement to caller`)" + errorLine1=" mConnectionlessHandwritingCallback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/inputmethodservice/InputMethodService.java" + line="2825" + column="60"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getAccessibilityBounceKeysThreshold()` is a flagged API and should be inside an `if (Flags.keyboardA11yBounceKeysFlag())` check (or annotate the surrounding method `isAccessibilityBounceKeysEnabled` with `@FlaggedApi(Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG) to transfer requirement to caller`)" + errorLine1=" return getAccessibilityBounceKeysThreshold(context) != 0;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/input/InputSettings.java" + line="458" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getAccessibilitySlowKeysThreshold()` is a flagged API and should be inside an `if (Flags.keyboardA11ySlowKeysFlag())` check (or annotate the surrounding method `isAccessibilitySlowKeysEnabled` with `@FlaggedApi(Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG) to transfer requirement to caller`)" + errorLine1=" return getAccessibilitySlowKeysThreshold(context) != 0;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/input/InputSettings.java" + line="542" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND` is a flagged API and should be inside an `if (Flags.customizableWindowHeaders())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS) to transfer requirement to caller`)" + errorLine1=" mask = APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/InsetsFlags.java" + line="76" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND` is a flagged API and should be inside an `if (Flags.customizableWindowHeaders())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS) to transfer requirement to caller`)" + errorLine1=" equals = APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/InsetsFlags.java" + line="77" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `APPEARANCE_LIGHT_CAPTION_BARS` is a flagged API and should be inside an `if (Flags.customizableWindowHeaders())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS) to transfer requirement to caller`)" + errorLine1=" mask = APPEARANCE_LIGHT_CAPTION_BARS," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/InsetsFlags.java" + line="80" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `APPEARANCE_LIGHT_CAPTION_BARS` is a flagged API and should be inside an `if (Flags.customizableWindowHeaders())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS) to transfer requirement to caller`)" + errorLine1=" equals = APPEARANCE_LIGHT_CAPTION_BARS," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/InsetsFlags.java" + line="81" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `countUriRelativeFilterGroups()` is a flagged API and should be inside an `if (Flags.relativeReferenceIntentFilters())` check (or annotate the surrounding method `toLongString` with `@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) to transfer requirement to caller`)" + errorLine1=" if (Flags.relativeReferenceIntentFilters() && countUriRelativeFilterGroups() > 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/IntentFilter.java" + line="577" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `matchGroupsToUri()` is a flagged API and should be inside an `if (Flags.relativeReferenceIntentFilters())` check (or annotate the surrounding method `matchRelRefGroups` with `@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) to transfer requirement to caller`)" + errorLine1=" return UriRelativeFilterGroup.matchGroupsToUri(mUriRelativeFilterGroups, data);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/IntentFilter.java" + line="1839" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isPrivateProfile()` is a flagged API and should be inside an `if (Flags.allowPrivateProfile())` check (or annotate the surrounding method `getPrivateProfile` with `@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) to transfer requirement to caller`)" + errorLine1=" if (userInfo.isPrivateProfile()) return userInfo;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/app/IntentForwarderActivity.java" + line="631" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `uptimeNanos()` is a flagged API and should be inside an `if (Flags.adpfGpuReportActualWorkDuration())` check (or annotate the surrounding method `postEventLogToWorkerThread` with `@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) to transfer requirement to caller`)" + errorLine1=" final long realtimeNanos = SystemClock.uptimeNanos();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/jank/InteractionJankMonitor.java" + line="606" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `KEYCODE_SCREENSHOT` is a flagged API and should be inside an `if (Flags.emojiAndScreenshotKeycodesAvailable())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE) to transfer requirement to caller`)" + errorLine1=" public static final int LAST_KEYCODE = KEYCODE_SCREENSHOT;" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/KeyEvent.java" + line="955" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isMgf1DigestsSpecified()` is a flagged API and should be inside an `if (Flags.mgf1DigestSetterV2())` check (or annotate the surrounding method `Builder` with `@FlaggedApi(Flags.FLAG_MGF1_DIGEST_SETTER_V2) to transfer requirement to caller`)" + errorLine1=" if (sourceSpec.isMgf1DigestsSpecified()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/keystore/KeyGenParameterSpec.java" + line="1025" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getMgf1Digests()` is a flagged API and should be inside an `if (Flags.mgf1DigestSetterV2())` check (or annotate the surrounding method `Builder` with `@FlaggedApi(Flags.FLAG_MGF1_DIGEST_SETTER_V2) to transfer requirement to caller`)" + errorLine1=" mMgf1Digests = sourceSpec.getMgf1Digests();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/keystore/KeyGenParameterSpec.java" + line="1026" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `BIOMETRIC_NO_AUTHENTICATION` is a flagged API and should be inside an `if (Flags.lastAuthenticationTime())` check (or annotate the surrounding method `getLastAuthTime` with `@FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME) to transfer requirement to caller`)" + errorLine1=" return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/KeyStoreAuthorization.java" + line="139" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `BIOMETRIC_NO_AUTHENTICATION` is a flagged API and should be inside an `if (Flags.lastAuthenticationTime())` check (or annotate the surrounding method `getLastAuthTime` with `@FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME) to transfer requirement to caller`)" + errorLine1=" return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/KeyStoreAuthorization.java" + line="145" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DATA_BLOCK_SERVICE` is a flagged API and should be inside an `if (Flags.frpEnforcement())` check (or annotate the surrounding method `createConfirmFactoryResetCredentialIntent` with `@FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) to transfer requirement to caller`)" + errorLine1=" ServiceManager.getService(Context.PERSISTENT_DATA_BLOCK_SERVICE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/KeyguardManager.java" + line="365" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isPrivateProfile()` is a flagged API and should be inside an `if (Flags.allowPrivateProfile())` check (or annotate the surrounding method `getProfiles` with `@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) to transfer requirement to caller`)" + errorLine1=" && mUserManager.isPrivateProfile())) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/LauncherApps.java" + line="712" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `computeDrawingBoundingBox()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `draw` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" RectF drawingRect = computeDrawingBoundingBox();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/Layout.java" + line="498" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getLineSpacingMultiplier()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `getSpacingMultiplier` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" return getLineSpacingMultiplier();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/Layout.java" + line="4341" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getLineSpacingAmount()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `getSpacingAdd` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" return getLineSpacingAmount();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/Layout.java" + line="4368" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="276" + column="71"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_WORD_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="280" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `HYPHENATION_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" private @Hyphenation int mHyphenation = LineBreakConfig.HYPHENATION_UNSPECIFIED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="282" + column="65"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `reset` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" mLineBreakStyle = LINE_BREAK_STYLE_UNSPECIFIED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="343" + column="35"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_WORD_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `reset` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_UNSPECIFIED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="344" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `HYPHENATION_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `reset` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" mHyphenation = HYPHENATION_UNSPECIFIED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="345" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_STYLE_AUTO` is a flagged API and should be inside an `if (Flags.wordStyleAuto())` check (or annotate the surrounding method `getResolvedLineBreakStyle` with `@FlaggedApi(Flags.FLAG_WORD_STYLE_AUTO) to transfer requirement to caller`)" + errorLine1=" defaultStyle = LINE_BREAK_STYLE_AUTO;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="492" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `getResolvedLineBreakStyle` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="499" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_WORD_STYLE_AUTO` is a flagged API and should be inside an `if (Flags.wordStyleAuto())` check (or annotate the surrounding method `getResolvedLineBreakWordStyle` with `@FlaggedApi(Flags.FLAG_WORD_STYLE_AUTO) to transfer requirement to caller`)" + errorLine1=" defaultWordStyle = LINE_BREAK_WORD_STYLE_AUTO;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="527" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_WORD_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `getResolvedLineBreakWordStyle` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="534" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `HYPHENATION_ENABLED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `getResolvedHyphenation` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" return HYPHENATION_ENABLED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="559" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `HYPHENATION_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `getResolvedHyphenation` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" return config.mHyphenation == HYPHENATION_UNSPECIFIED" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="561" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `HYPHENATION_ENABLED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `getResolvedHyphenation` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" ? HYPHENATION_ENABLED : config.mHyphenation;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/LineBreakConfig.java" + line="562" + column="19"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getSessionId()` is a flagged API and should be inside an `if (Flags.loudnessConfiguratorApi())` check (or annotate the surrounding method `dispatchLoudnessCodecParameterChange` with `@FlaggedApi(Flags.FLAG_LOUDNESS_CONFIGURATOR_API) to transfer requirement to caller`)" + errorLine1=" if (lcConfig.getSessionId() == sessionId) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/LoudnessCodecDispatcher.java" + line="85" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `mediaCodecsConsume()` is a flagged API and should be inside an `if (Flags.loudnessConfiguratorApi())` check (or annotate the surrounding method `dispatchLoudnessCodecParameterChange` with `@FlaggedApi(Flags.FLAG_LOUDNESS_CONFIGURATOR_API) to transfer requirement to caller`)" + errorLine1=" lcConfig.mediaCodecsConsume(mcEntry -> {" + errorLine2=" ^"> + <location + file="frameworks/base/media/java/android/media/LoudnessCodecDispatcher.java" + line="86" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onLoudnessCodecUpdate()` is a flagged API and should be inside an `if (Flags.loudnessConfiguratorApi())` check (or annotate the surrounding method `dispatchLoudnessCodecParameterChange` with `@FlaggedApi(Flags.FLAG_LOUDNESS_CONFIGURATOR_API) to transfer requirement to caller`)" + errorLine1=" l.onLoudnessCodecUpdate(mediaCodec," + errorLine2=" ^"> + <location + file="frameworks/base/media/java/android/media/LoudnessCodecDispatcher.java" + line="110" + column="53"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.provider.user_keys")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="29" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.provider.user_keys")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="29" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.provider.user_keys")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="29" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.provider.user_keys")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="29" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="552" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="561" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="992" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.net.platform.flags.register_nsd_offload_engine")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1055" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.quarantined_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1101" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.quarantined_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1101" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.screen_recording_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1310" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.screen_recording_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1310" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.screen_recording_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1310" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.screen_recording_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1310" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.screen_recording_callbacks")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1310" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.security.frp_enforcement")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1434" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.security.frp_enforcement")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1434" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1639" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1726" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1726" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1726" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1726" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1839" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="1848" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2292" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2300" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.security_log_v2_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2363" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.security_log_v2_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2363" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.security_log_v2_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2363" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.security_log_v2_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2363" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.security_log_v2_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2363" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.device_theft_api_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2489" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.device_theft_api_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2489" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2523" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2523" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2523" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.esim_management_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2532" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.esim_management_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2532" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.esim_management_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2532" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.esim_management_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2532" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2539" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2546" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2553" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2596" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2596" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2596" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2661" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.always_update_wallpaper_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2904" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.always_update_wallpaper_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="2904" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="3364" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="3364" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="3364" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.accessibility.motion_event_observing")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="3584" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.media.tv.flags.enable_ad_service_fw")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="3971" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.get_resolved_apk_path")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4252" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.get_resolved_apk_path")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4252" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.device_presence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4465" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.device_presence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4465" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.flags.companion_transport_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4475" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.flags.companion_transport_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4475" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.flags.companion_transport_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4475" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.flags.companion_transport_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4475" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.flags.companion_transport_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4475" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.flags.companion_transport_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4475" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4780" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4780" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4780" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4780" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4780" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4780" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4780" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.usage.report_usage_stats_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4938" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.usage.report_usage_stats_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4938" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.usage.report_usage_stats_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4938" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.usage.report_usage_stats_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="4938" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5392" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.sensitive_notification_app_protection")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5535" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5706" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5714" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.contextualsearch.flags.enable_service")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5829" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.contextualsearch.flags.enable_service")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5829" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.introduce_media_processing_type")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5935" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.introduce_media_processing_type")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5935" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.introduce_media_processing_type")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="5935" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.voice_activation_permission_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6084" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.voice_activation_permission_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6084" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.nfc.enable_nfc_mainline")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6304" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6544" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6552" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6560" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.get_binding_uid_importance")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6660" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.get_binding_uid_importance")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6660" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.factory_reset_prep_permission_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6676" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.input.flags.override_key_behavior_permission_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6693" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.flags.sensitive_content_app_protection_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6704" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.bic_client")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6715" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.bic_client")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6715" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.system_terms_of_address_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6723" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.system_terms_of_address_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6723" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.app.system_terms_of_address_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6723" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.emergency_install_permission")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6732" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.server.power.feature.flags.enable_early_screen_timeout_detector")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6747" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.security.fsverity_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/Manifest.java" + line="6755" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `LineBreakConfigSpan` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `buildForMeasurement` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" LineBreakConfigSpan.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="471" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `LineBreakConfigSpan` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `buildForMeasurement` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" LineBreakConfigSpan.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="476" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `LineBreakConfigSpan` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `buildForMeasurement` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" LineBreakConfigSpan.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="479" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `LineBreakConfigSpan` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `buildForStaticLayoutInternal` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" LineBreakConfigSpan.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="625" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `LineBreakConfigSpan` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `buildForStaticLayoutInternal` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" LineBreakConfigSpan.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="630" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `LineBreakConfigSpan` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `buildForStaticLayoutInternal` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" LineBreakConfigSpan.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="634" + column="56"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onAppendReplacementRun()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyReplacementRun` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" testCallback.onAppendReplacementRun(paint, end - start, width);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="784" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `applyStyleRun` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="802" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `applyStyleRun` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="802" + column="66"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onAppendStyleRun()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyleRun` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" testCallback.onAppendStyleRun(paint, config, end - start, false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="814" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `applyStyleRun` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="828" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `applyStyleRun` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="828" + column="74"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onAppendStyleRun()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyleRun` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" testCallback.onAppendStyleRun(paint, config, levelEnd - levelStart, isRtl);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="840" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getLineBreakConfig()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyMetricsAffectingSpan` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" mLineBreakConfigBuilder.merge(lbcSpan.getLineBreakConfig());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="888" + column="47"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `merge()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyMetricsAffectingSpan` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" mLineBreakConfigBuilder.merge(lbcSpan.getLineBreakConfig());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/MeasuredParagraph.java" + line="888" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `HYPHENATION_ENABLED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `appendStyleRun` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" == LineBreakConfig.HYPHENATION_ENABLED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/text/MeasuredText.java" + line="319" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onOutputBuffersAvailable()` is a flagged API and should be inside an `if (Flags.largeAudioFrame())` check (or annotate the surrounding method `handleCallback` with `@FlaggedApi(Flags.FLAG_LARGE_AUDIO_FRAME) to transfer requirement to caller`)" + errorLine1=" mCallback.onOutputBuffersAvailable(" + errorLine2=" ^"> + <location + file="frameworks/base/media/java/android/media/MediaCodec.java" + line="1985" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONFIGURE_FLAG_DETACHED_SURFACE` is a flagged API and should be inside an `if (Flags.nullOutputSurface())` check (or annotate the surrounding method `configure` with `@FlaggedApi(Flags.FLAG_NULL_OUTPUT_SURFACE) to transfer requirement to caller`)" + errorLine1=" if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0 && !canDetach) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaCodec.java" + line="2371" + column="45"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONFIGURE_FLAG_DETACHED_SURFACE` is a flagged API and should be inside an `if (Flags.nullOutputSurface())` check (or annotate the surrounding method `configure` with `@FlaggedApi(Flags.FLAG_NULL_OUTPUT_SURFACE) to transfer requirement to caller`)" + errorLine1=" if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaCodec.java" + line="2422" + column="45"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FEATURE_DynamicColorAspects` is a flagged API and should be inside an `if (Flags.dynamicColorAspects())` check (or annotate the surrounding method `getDecoderFeatures` with `@FlaggedApi(Flags.FLAG_DYNAMIC_COLOR_ASPECTS) to transfer requirement to caller`)" + errorLine1=" features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaCodecInfo.java" + line="832" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FEATURE_DetachedSurface` is a flagged API and should be inside an `if (Flags.nullOutputSurface())` check (or annotate the surrounding method `getDecoderFeatures` with `@FlaggedApi(Flags.FLAG_NULL_OUTPUT_SURFACE) to transfer requirement to caller`)" + errorLine1=" features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaCodecInfo.java" + line="835" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FEATURE_HlgEditing` is a flagged API and should be inside an `if (Flags.hlgEditing())` check (or annotate the surrounding method `getEncoderFeatures` with `@FlaggedApi(Flags.FLAG_HLG_EDITING) to transfer requirement to caller`)" + errorLine1=" features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaCodecInfo.java" + line="856" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FEATURE_Roi` is a flagged API and should be inside an `if (Flags.regionOfInterest())` check (or annotate the surrounding method `getEncoderFeatures` with `@FlaggedApi(Flags.FLAG_REGION_OF_INTEREST) to transfer requirement to caller`)" + errorLine1=" features.add(new Feature(FEATURE_Roi, (1 << 7), true));" + errorLine2=" ~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaCodecInfo.java" + line="859" + column="46"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_HDMI_ARC` is a flagged API and should be inside an `if (Flags.enableAudioPoliciesDeviceAndBluetoothController())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) to transfer requirement to caller`)" + errorLine1=" case TYPE_HDMI_ARC:" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1030" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_HDMI_EARC` is a flagged API and should be inside an `if (Flags.enableAudioPoliciesDeviceAndBluetoothController())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) to transfer requirement to caller`)" + errorLine1=" case TYPE_HDMI_EARC:" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1032" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_REMOTE_TABLET` is a flagged API and should be inside an `if (Flags.enableNewMediaRoute2InfoTypes())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES) to transfer requirement to caller`)" + errorLine1=" case TYPE_REMOTE_TABLET:" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1050" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_REMOTE_TABLET_DOCKED` is a flagged API and should be inside an `if (Flags.enableNewMediaRoute2InfoTypes())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES) to transfer requirement to caller`)" + errorLine1=" case TYPE_REMOTE_TABLET_DOCKED:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1052" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_REMOTE_COMPUTER` is a flagged API and should be inside an `if (Flags.enableNewMediaRoute2InfoTypes())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES) to transfer requirement to caller`)" + errorLine1=" case TYPE_REMOTE_COMPUTER:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1054" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_REMOTE_GAME_CONSOLE` is a flagged API and should be inside an `if (Flags.enableNewMediaRoute2InfoTypes())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES) to transfer requirement to caller`)" + errorLine1=" case TYPE_REMOTE_GAME_CONSOLE:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1056" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_REMOTE_CAR` is a flagged API and should be inside an `if (Flags.enableNewMediaRoute2InfoTypes())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES) to transfer requirement to caller`)" + errorLine1=" case TYPE_REMOTE_CAR:" + errorLine2=" ~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1058" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_REMOTE_SMARTWATCH` is a flagged API and should be inside an `if (Flags.enableNewMediaRoute2InfoTypes())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES) to transfer requirement to caller`)" + errorLine1=" case TYPE_REMOTE_SMARTWATCH:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1060" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_REMOTE_SMARTPHONE` is a flagged API and should be inside an `if (Flags.enableNewMediaRoute2InfoTypes())` check (or annotate the surrounding method `getDeviceTypeString` with `@FlaggedApi(Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES) to transfer requirement to caller`)" + errorLine1=" case TYPE_REMOTE_SMARTPHONE:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1062" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER` is a flagged API and should be inside an `if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses())` check (or annotate the surrounding method `Builder` with `@FlaggedApi(Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) to transfer requirement to caller`)" + errorLine1=" mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRoute2Info.java" + line="1122" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `OPSTR_MEDIA_ROUTING_CONTROL` is a flagged API and should be inside an `if (Flags.enablePrivilegedRoutingForMediaRoutingControl())` check (or annotate the surrounding method `checkCallerHasOnlyRevocablePermissions` with `@FlaggedApi(Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL) to transfer requirement to caller`)" + errorLine1=" AppOpsManager.OPSTR_MEDIA_ROUTING_CONTROL," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/MediaRouter2.java" + line="457" + column="47"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY` is a flagged API and should be inside an `if (Flags.businessCallComposer())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER) to transfer requirement to caller`)" + errorLine1=" CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY + 1;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/ims/feature/MmTelFeature.java" + line="574" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY` is a flagged API and should be inside an `if (Flags.businessCallComposer())` check (or annotate the surrounding method `toString` with `@FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER) to transfer requirement to caller`)" + errorLine1=" builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/ims/feature/MmTelFeature.java" + line="622" + column="38"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isReliable()` is a flagged API and should be inside an `if (Flags.reliableMessage())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE) to transfer requirement to caller`)" + errorLine1=" || (other.isReliable() == mIsReliable))" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/location/NanoAppMessage.java" + line="269" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getMessageSequenceNumber()` is a flagged API and should be inside an `if (Flags.reliableMessage())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE) to transfer requirement to caller`)" + errorLine1=" || (other.getMessageSequenceNumber() == mMessageSequenceNumber));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/location/NanoAppMessage.java" + line="271" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SERVICE_TYPE_MMS` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_MMS;" + errorLine2=" ~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/NetworkRegistrationInfo.java" + line="219" + column="49"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SERVICE_TYPE_MMS` is a flagged API and should be inside an `if (Flags.carrierEnabledSatelliteFlag())` check (or annotate the surrounding method `serviceTypeToString` with `@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" case SERVICE_TYPE_MMS: return "MMS";" + errorLine2=" ~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/NetworkRegistrationInfo.java" + line="759" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getVibrationEffect()` is a flagged API and should be inside an `if (Flags.notificationChannelVibrationEffectApi())` check (or annotate the surrounding method `writeXml` with `@FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API) to transfer requirement to caller`)" + errorLine1=" if (getVibrationEffect() != null) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationChannel.java" + line="1333" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getVibrationEffect()` is a flagged API and should be inside an `if (Flags.notificationChannelVibrationEffectApi())` check (or annotate the surrounding method `writeXml` with `@FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API) to transfer requirement to caller`)" + errorLine1=" out.attribute(null, ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect()));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationChannel.java" + line="1334" + column="73"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getVibrationEffect()` is a flagged API and should be inside an `if (Flags.notificationChannelVibrationEffectApi())` check (or annotate the surrounding method `toJson` with `@FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API) to transfer requirement to caller`)" + errorLine1=" if (getVibrationEffect() != null) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationChannel.java" + line="1416" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getVibrationEffect()` is a flagged API and should be inside an `if (Flags.notificationChannelVibrationEffectApi())` check (or annotate the surrounding method `toJson` with `@FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API) to transfer requirement to caller`)" + errorLine1=" record.put(ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect()));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationChannel.java" + line="1417" + column="64"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getVibrationEffect()` is a flagged API and should be inside an `if (Flags.notificationChannelVibrationEffectApi())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API) to transfer requirement to caller`)" + errorLine1=" && Objects.equals(getVibrationEffect(), that.getVibrationEffect())" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationChannel.java" + line="1545" + column="35"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getVibrationEffect()` is a flagged API and should be inside an `if (Flags.notificationChannelVibrationEffectApi())` check (or annotate the surrounding method `equals` with `@FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API) to transfer requirement to caller`)" + errorLine1=" && Objects.equals(getVibrationEffect(), that.getVibrationEffect())" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationChannel.java" + line="1545" + column="57"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getVibrationEffect()` is a flagged API and should be inside an `if (Flags.notificationChannelVibrationEffectApi())` check (or annotate the surrounding method `hashCode` with `@FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API) to transfer requirement to caller`)" + errorLine1=" mImportanceLockedDefaultApp, mOriginalImportance, getVibrationEffect()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationChannel.java" + line="1563" + column="67"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `addAutomaticZenRule()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `addAutomaticZenRule` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" return addAutomaticZenRule(automaticZenRule, /* fromUser= */ false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationManager.java" + line="1368" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `updateAutomaticZenRule()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `updateAutomaticZenRule` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" return updateAutomaticZenRule(id, automaticZenRule, /* fromUser= */ false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationManager.java" + line="1404" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `removeAutomaticZenRule()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `removeAutomaticZenRule` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" return removeAutomaticZenRule(id, /* fromUser= */ false);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/NotificationManager.java" + line="1473" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `ROLLBACK_USER_IMPACT_LOW` is a flagged API and should be inside an `if (Flags.recoverabilityDetection())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION) to transfer requirement to caller`)" + errorLine1=" public int rollbackImpactLevel = PackageManager.ROLLBACK_USER_IMPACT_LOW;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/PackageInstaller.java" + line="2789" + column="57"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `isArchived` is a flagged API and should be inside an `if (Flags.archiving())` check (or annotate the surrounding method `PackageItemInfo` with `@FlaggedApi(Flags.FLAG_ARCHIVING) to transfer requirement to caller`)" + errorLine1=" isArchived = orig.isArchived;" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/PackageItemInfo.java" + line="204" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `isArchived` is a flagged API and should be inside an `if (Flags.archiving())` check (or annotate the surrounding method `PackageItemInfo` with `@FlaggedApi(Flags.FLAG_ARCHIVING) to transfer requirement to caller`)" + errorLine1=" isArchived = orig.isArchived;" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/PackageItemInfo.java" + line="204" + column="27"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `isArchived` is a flagged API and should be inside an `if (Flags.archiving())` check (or annotate the surrounding method `writeToParcel` with `@FlaggedApi(Flags.FLAG_ARCHIVING) to transfer requirement to caller`)" + errorLine1=" dest.writeBoolean(isArchived);" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/PackageItemInfo.java" + line="470" + column="27"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `isArchived` is a flagged API and should be inside an `if (Flags.archiving())` check (or annotate the surrounding method `dumpDebug` with `@FlaggedApi(Flags.FLAG_ARCHIVING) to transfer requirement to caller`)" + errorLine1=" proto.write(PackageItemInfoProto.IS_ARCHIVED, isArchived);" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/PackageItemInfo.java" + line="488" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `isArchived` is a flagged API and should be inside an `if (Flags.archiving())` check (or annotate the surrounding method `PackageItemInfo` with `@FlaggedApi(Flags.FLAG_ARCHIVING) to transfer requirement to caller`)" + errorLine1=" isArchived = source.readBoolean();" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/PackageItemInfo.java" + line="503" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `onPermissionsChanged` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/PackageManager.java" + line="766" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `EXTRA_ARCHIVAL` is a flagged API and should be inside an `if (Flags.archiving())` check (or annotate the surrounding method `doHandlePackageEvent` with `@FlaggedApi(Flags.FLAG_ARCHIVING) to transfer requirement to caller`)" + errorLine1=" if (intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false)) {" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/content/PackageMonitor.java" + line="491" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `ACTION_PACKAGE_UNSTOPPED` is a flagged API and should be inside an `if (Flags.stayStopped())` check (or annotate the surrounding method `doHandlePackageEvent` with `@FlaggedApi(Flags.FLAG_STAY_STOPPED) to transfer requirement to caller`)" + errorLine1=" } else if (Intent.ACTION_PACKAGE_UNSTOPPED.equals(action)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/content/PackageMonitor.java" + line="582" + column="27"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `measureText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Paint.java" + line="2587" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `measureText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Paint.java" + line="2587" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `measureText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Paint.java" + line="2626" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `measureText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Paint.java" + line="2626" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `getTextWidths` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Paint.java" + line="2846" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `getTextWidths` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Paint.java" + line="2846" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `getTextWidths` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Paint.java" + line="2936" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `getTextWidths` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Paint.java" + line="2936" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isMgf1DigestsSpecified()` is a flagged API and should be inside an `if (Flags.mgf1DigestSetterV2())` check (or annotate the surrounding method `writeToParcel` with `@FlaggedApi(Flags.FLAG_MGF1_DIGEST_SETTER_V2) to transfer requirement to caller`)" + errorLine1=" if (mSpec.isMgf1DigestsSpecified()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java" + line="98" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getMgf1Digests()` is a flagged API and should be inside an `if (Flags.mgf1DigestSetterV2())` check (or annotate the surrounding method `writeToParcel` with `@FlaggedApi(Flags.FLAG_MGF1_DIGEST_SETTER_V2) to transfer requirement to caller`)" + errorLine1=" out.writeStringList(List.copyOf(mSpec.getMgf1Digests()));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java" + line="99" + column="45"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `computeBounds()` is a flagged API and should be inside an `if (Flags.exactComputeBounds())` check (or annotate the surrounding method `computeBounds` with `@FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS) to transfer requirement to caller`)" + errorLine1=" computeBounds(bounds);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/Path.java" + line="310" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onOneTimePermissionSessionTimeout()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `notifyOneTimePermissionSessionTimeout` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" onOneTimePermissionSessionTimeout(packageName, deviceId);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/permission/PermissionControllerService.java" + line="672" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onRevokeSelfPermissionsOnKill()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `revokeSelfPermissionsOnKill` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" onRevokeSelfPermissionsOnKill(packageName, permissions, deviceId," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/permission/PermissionControllerService.java" + line="776" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `getIndicatorAppOpUsageData` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/permission/PermissionManager.java" + line="1342" + column="38"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `getPersistentDeviceId` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" persistentDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/permission/PermissionManager.java" + line="1951" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onPermissionsChanged()` is a flagged API and should be inside an `if (Flags.deviceAwarePermissionApisEnabled())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) to transfer requirement to caller`)" + errorLine1=" mListener.onPermissionsChanged(uid, persistentDeviceId);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/permission/PermissionManager.java" + line="2040" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPersistentDeviceId()` is a flagged API and should be inside an `if (Flags.vdmPublicApis())` check (or annotate the surrounding method `getOpUsageDataForAllDevices` with `@FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) to transfer requirement to caller`)" + errorLine1=" persistentDeviceIds.add(virtualDevices.get(num).getPersistentDeviceId());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/permission/PermissionUsageHelper.java" + line="381" + column="37"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `getOpUsageDataForAllDevices` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" persistentDeviceIds.add(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/permission/PermissionUsageHelper.java" + line="383" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `getOpUsagesByDevice` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/permission/PermissionUsageHelper.java" + line="492" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `hasSimultaneousCallingRestriction()` is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `Builder` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" if (phoneAccount.hasSimultaneousCallingRestriction()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/PhoneAccount.java" + line="585" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getSimultaneousCallingRestriction()` is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `Builder` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" mSimultaneousCallingRestriction = phoneAccount.getSimultaneousCallingRestriction();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/PhoneAccount.java" + line="586" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `hasSimultaneousCallingRestriction()` is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `toString` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" if (hasSimultaneousCallingRestriction()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telecomm/java/android/telecom/PhoneAccount.java" + line="1292" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `STATE_PLAYBACK_SUPPRESSED` is a flagged API and should be inside an `if (Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange())` check (or annotate the surrounding method `isActive` with `@FlaggedApi(Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE) to transfer requirement to caller`)" + errorLine1=" case PlaybackState.STATE_PLAYBACK_SUPPRESSED:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/session/PlaybackState.java" + line="541" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `STATE_PLAYBACK_SUPPRESSED` is a flagged API and should be inside an `if (Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange())` check (or annotate the surrounding method `getStringForStateInt` with `@FlaggedApi(Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE) to transfer requirement to caller`)" + errorLine1=" case STATE_PLAYBACK_SUPPRESSED:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/session/PlaybackState.java" + line="587" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="39" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="49" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="49" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="57" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="57" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="94" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="103" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="103" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="115" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="123" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitor.java" + line="129" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1="@FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="29" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="31" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="31" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="60" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/PowerMonitorReadings.java" + line="72" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_UNSUPPORTED` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `PreciseDataConnectionState` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" .build(), null, NETWORK_VALIDATION_UNSUPPORTED);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/PreciseDataConnectionState.java" + line="143" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_UNSUPPORTED` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `networkValidationStatusToString` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" case NETWORK_VALIDATION_UNSUPPORTED: return "unsupported";" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/PreciseDataConnectionState.java" + line="456" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_NOT_REQUESTED` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `networkValidationStatusToString` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" case NETWORK_VALIDATION_NOT_REQUESTED: return "not requested";" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/PreciseDataConnectionState.java" + line="457" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_IN_PROGRESS` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `networkValidationStatusToString` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" case NETWORK_VALIDATION_IN_PROGRESS: return "in progress";" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/PreciseDataConnectionState.java" + line="458" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_SUCCESS` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `networkValidationStatusToString` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" case NETWORK_VALIDATION_SUCCESS: return "success";" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/PreciseDataConnectionState.java" + line="459" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_FAILURE` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `networkValidationStatusToString` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" case NETWORK_VALIDATION_FAILURE: return "failure";" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/PreciseDataConnectionState.java" + line="460" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `NETWORK_VALIDATION_UNSUPPORTED` is a flagged API and should be inside an `if (Flags.networkValidation())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) to transfer requirement to caller`)" + errorLine1=" NETWORK_VALIDATION_UNSUPPORTED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/PreciseDataConnectionState.java" + line="508" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_WORD_STYLE_AUTO` is a flagged API and should be inside an `if (Flags.wordStyleAuto())` check (or annotate the surrounding method `createMeasuredParagraphsFromPrecomputedText` with `@FlaggedApi(Flags.FLAG_WORD_STYLE_AUTO) to transfer requirement to caller`)" + errorLine1=" if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/PrecomputedText.java" + line="461" + column="63"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `merge()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `createMeasuredParagraphsFromPrecomputedText` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" config = new LineBreakConfig.Builder()" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/text/PrecomputedText.java" + line="464" + column="22"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_WORD_STYLE_AUTO` is a flagged API and should be inside an `if (Flags.wordStyleAuto())` check (or annotate the surrounding method `createMeasuredParagraphs` with `@FlaggedApi(Flags.FLAG_WORD_STYLE_AUTO) to transfer requirement to caller`)" + errorLine1=" if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/PrecomputedText.java" + line="515" + column="71"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isResumed()` is a flagged API and should be inside an `if (Flags.enableNfcMainline())` check (or annotate the surrounding method `onListItemClick` with `@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) to transfer requirement to caller`)" + errorLine1=" if (!isResumed()) {" + errorLine2=" ~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/preference/PreferenceActivity.java" + line="1071" + column="14"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `PromptContentViewParcelable` is a flagged API and should be inside an `if (Flags.customBiometricPrompt())` check (or annotate the surrounding method `PromptInfo` with `@FlaggedApi(Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT) to transfer requirement to caller`)" + errorLine1=" mContentView = in.readParcelable(PromptContentViewParcelable.class.getClassLoader()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java" + line="75" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `PromptContentViewParcelable` is a flagged API and should be inside an `if (Flags.customBiometricPrompt())` check (or annotate the surrounding method `PromptInfo` with `@FlaggedApi(Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT) to transfer requirement to caller`)" + errorLine1=" PromptContentViewParcelable.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java" + line="76" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.customBiometricPrompt())` check (or annotate the surrounding method `isContentViewMoreOptionsButtonUsed` with `@FlaggedApi(Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT) to transfer requirement to caller`)" + errorLine1=" && mContentView instanceof PromptContentViewWithMoreOptionsButton;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java" + line="214" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.customBiometricPrompt())` check (or annotate the surrounding method `setContentView` with `@FlaggedApi(Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT) to transfer requirement to caller`)" + errorLine1=" mContentView = (PromptContentViewParcelable) view;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java" + line="258" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.media.tv.flags.enable_ad_service_fw")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="610" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.media.tv.flags.enable_ad_service_fw")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="610" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.media.tv.flags.enable_ad_service_fw")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="610" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.media.tv.flags.enable_ad_service_fw")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="610" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="693" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.security.asm_restrictions_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="759" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.nfc.nfc_read_polling_loop")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="1300" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.flags.sensitive_content_app_protection_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="2782" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.res.default_locale")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="3023" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="3599" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="3599" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="3599" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="3599" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.res.manifest_flagging")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="4290" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="4870" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="4970" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="4979" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="5032" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.virtual.flags.vdm_custom_ime")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="6857" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.inputmethod.ime_switcher_revamp")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="7587" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.inputmethod.ime_switcher_revamp")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="7587" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.inputmethod.ime_switcher_revamp")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="7587" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.inputmethod.ime_switcher_revamp")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="7587" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.sdk_lib_independence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="9481" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.sdk_lib_independence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="9481" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.sdk_lib_independence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="9481" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.sdk_lib_independence")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="9481" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="10701" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="10727" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="10767" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="10776" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.pm.relative_reference_intent_filters")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="10785" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.security.content_uri_permission_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="11122" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.use_bounds_for_width")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="12404" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.nfc.nfc_observe_mode")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="12438" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.inputmethod.connectionless_handwriting")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="13485" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="13672" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.use_bounds_for_width")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="15472" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.fix_line_height_for_locale")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="15520" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="16364" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.window.flags.enforce_edge_to_edge")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="16561" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.retail_demo_role_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="18455" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.permission.flags.wallet_role_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="18473" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.security.content_uri_permission_apis")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="25110" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.security.asm_restrictions_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="25837" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.security.asm_restrictions_enabled")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="27173" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.inputmethod.ime_switcher_revamp")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="41370" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.companion.virtual.flags.vdm_custom_ime")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="41608" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.inputmethod.ime_switcher_revamp")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="41645" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.content.res.default_locale")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="44145" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.nfc.nfc_read_polling_loop")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="45932" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.nfc.nfc_read_polling_loop")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="45956" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.nfc.nfc_read_polling_loop")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="45970" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.nfc.nfc_read_polling_loop")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="45996" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.use_bounds_for_width")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="53416" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.use_bounds_for_width")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="53416" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.use_bounds_for_width")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="53416" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.use_bounds_for_width")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="53416" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.use_bounds_for_width")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="55227" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.fix_line_height_for_locale")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="55238" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("com.android.text.flags.use_bounds_for_width")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="55249" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.flags.sensitive_content_app_protection_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="62287" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @android.annotation.FlaggedApi("android.view.flags.sensitive_content_app_protection_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/android/R.srcjar!/android/R.java" + line="64240" + column="36"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `METADATA_KEY_COMMENT_SHORT_DESCRIPTION` is a flagged API and should be inside an `if (Flags.hdRadioImproved())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) to transfer requirement to caller`)" + errorLine1=" METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_SHORT_DESCRIPTION, METADATA_TYPE_TEXT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/radio/RadioMetadata.java" + line="249" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `METADATA_KEY_COMMENT_ACTUAL_TEXT` is a flagged API and should be inside an `if (Flags.hdRadioImproved())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) to transfer requirement to caller`)" + errorLine1=" METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_ACTUAL_TEXT, METADATA_TYPE_TEXT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/radio/RadioMetadata.java" + line="250" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `METADATA_KEY_COMMERCIAL` is a flagged API and should be inside an `if (Flags.hdRadioImproved())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) to transfer requirement to caller`)" + errorLine1=" METADATA_KEYS_TYPE.put(METADATA_KEY_COMMERCIAL, METADATA_TYPE_TEXT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/radio/RadioMetadata.java" + line="251" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `METADATA_KEY_UFIDS` is a flagged API and should be inside an `if (Flags.hdRadioImproved())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) to transfer requirement to caller`)" + errorLine1=" METADATA_KEYS_TYPE.put(METADATA_KEY_UFIDS, METADATA_TYPE_TEXT_ARRAY);" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/radio/RadioMetadata.java" + line="252" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `METADATA_KEY_HD_STATION_NAME_SHORT` is a flagged API and should be inside an `if (Flags.hdRadioImproved())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) to transfer requirement to caller`)" + errorLine1=" METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_SHORT, METADATA_TYPE_TEXT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/radio/RadioMetadata.java" + line="253" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `METADATA_KEY_HD_STATION_NAME_LONG` is a flagged API and should be inside an `if (Flags.hdRadioImproved())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) to transfer requirement to caller`)" + errorLine1=" METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_LONG, METADATA_TYPE_TEXT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/radio/RadioMetadata.java" + line="254" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `METADATA_KEY_HD_SUBCHANNELS_AVAILABLE` is a flagged API and should be inside an `if (Flags.hdRadioImproved())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) to transfer requirement to caller`)" + errorLine1=" METADATA_KEYS_TYPE.put(METADATA_KEY_HD_SUBCHANNELS_AVAILABLE, METADATA_TYPE_INT);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/radio/RadioMetadata.java" + line="255" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isPrivateProfile()` is a flagged API and should be inside an `if (Flags.allowPrivateProfile())` check (or annotate the surrounding method `fetchPrivateProfileUserHandle` with `@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) to transfer requirement to caller`)" + errorLine1=" if (userInfo.isPrivateProfile()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/app/ResolverActivity.java" + line="827" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDefaultLocale()` is a flagged API and should be inside an `if (Flags.defaultLocale())` check (or annotate the surrounding method `updateConfigurationImpl` with `@FlaggedApi(Flags.FLAG_DEFAULT_LOCALE) to transfer requirement to caller`)" + errorLine1=" if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/ResourcesImpl.java" + line="474" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDefaultLocale()` is a flagged API and should be inside an `if (Flags.defaultLocale())` check (or annotate the surrounding method `updateConfigurationImpl` with `@FlaggedApi(Flags.FLAG_DEFAULT_LOCALE) to transfer requirement to caller`)" + errorLine1=" if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/ResourcesImpl.java" + line="515" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `ROLLBACK_USER_IMPACT_LOW` is a flagged API and should be inside an `if (Flags.recoverabilityDetection())` check (or annotate the surrounding method `RollbackInfo` with `@FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION) to transfer requirement to caller`)" + errorLine1=" PackageManager.ROLLBACK_USER_IMPACT_LOW);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/rollback/RollbackInfo.java" + line="80" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getTransferReason()` is a flagged API and should be inside an `if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses())` check (or annotate the surrounding method `toString` with `@FlaggedApi(Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) to transfer requirement to caller`)" + errorLine1=" .append(getTransferReason())" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/RoutingSessionInfo.java" + line="528" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TRANSFER_REASON_FALLBACK` is a flagged API and should be inside an `if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) to transfer requirement to caller`)" + errorLine1=" @TransferReason private int mTransferReason = TRANSFER_REASON_FALLBACK;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/RoutingSessionInfo.java" + line="590" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SCREEN_RECORDING_STATE_NOT_VISIBLE` is a flagged API and should be inside an `if (Flags.screenRecordingCallbacks())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SCREEN_RECORDING_CALLBACKS) to transfer requirement to caller`)" + errorLine1=" private @ScreenRecordingState int mState = SCREEN_RECORDING_STATE_NOT_VISIBLE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ScreenRecordingCallbacks.java" + line="54" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SCREEN_RECORDING_STATE_VISIBLE` is a flagged API and should be inside an `if (Flags.screenRecordingCallbacks())` check (or annotate the surrounding method `onScreenRecordingStateChanged` with `@FlaggedApi(Flags.FLAG_SCREEN_RECORDING_CALLBACKS) to transfer requirement to caller`)" + errorLine1=" ? SCREEN_RECORDING_STATE_VISIBLE" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ScreenRecordingCallbacks.java" + line="85" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SCREEN_RECORDING_STATE_NOT_VISIBLE` is a flagged API and should be inside an `if (Flags.screenRecordingCallbacks())` check (or annotate the surrounding method `onScreenRecordingStateChanged` with `@FlaggedApi(Flags.FLAG_SCREEN_RECORDING_CALLBACKS) to transfer requirement to caller`)" + errorLine1=" : SCREEN_RECORDING_STATE_NOT_VISIBLE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ScreenRecordingCallbacks.java" + line="86" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SCREEN_RECORDING_STATE_VISIBLE` is a flagged API and should be inside an `if (Flags.screenRecordingCallbacks())` check (or annotate the surrounding method `addCallback` with `@FlaggedApi(Flags.FLAG_SCREEN_RECORDING_CALLBACKS) to transfer requirement to caller`)" + errorLine1=" ? SCREEN_RECORDING_STATE_VISIBLE" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ScreenRecordingCallbacks.java" + line="96" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SCREEN_RECORDING_STATE_NOT_VISIBLE` is a flagged API and should be inside an `if (Flags.screenRecordingCallbacks())` check (or annotate the surrounding method `addCallback` with `@FlaggedApi(Flags.FLAG_SCREEN_RECORDING_CALLBACKS) to transfer requirement to caller`)" + errorLine1=" : SCREEN_RECORDING_STATE_NOT_VISIBLE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ScreenRecordingCallbacks.java" + line="97" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING` is a flagged API and should be inside an `if (Flags.introduceMediaProcessingType())` check (or annotate the surrounding method `foregroundServiceTypeToLabel` with `@FlaggedApi(Flags.FLAG_INTRODUCE_MEDIA_PROCESSING_TYPE) to transfer requirement to caller`)" + errorLine1=" case FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/ServiceInfo.java" + line="707" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isPrivateProfile()` is a flagged API and should be inside an `if (Flags.allowPrivateProfile())` check (or annotate the surrounding method `setLaunchUserSpecificMessage` with `@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) to transfer requirement to caller`)" + errorLine1=" if (userInfo != null && userInfo.isPrivateProfile()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/app/SetScreenLockDialogActivity.java" + line="146" + column="37"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/provider/Settings.java" + line="665" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.biometrics.face_vhal_feature")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/provider/Settings.java" + line="11022" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.biometrics.face_vhal_feature")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/provider/Settings.java" + line="11030" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java" + line="300" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getInputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `createSurfaceView` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" : attachedSurfaceControl.getInputTransferToken()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/window/SplashScreenView.java" + line="342" + column="35"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `uptimeNanos()` is a flagged API and should be inside an `if (Flags.adpfGpuReportActualWorkDuration())` check (or annotate the surrounding method `getTime` with `@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) to transfer requirement to caller`)" + errorLine1=" return SystemClock.uptimeNanos() / 1000;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/util/StatLogger.java" + line="94" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `generate` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" final LineBreaker lineBreaker = new LineBreaker.Builder()" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/text/StaticLayout.java" + line="823" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `computeDrawingBoundingBox()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `computeDrawingBoundingBox` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" mDrawingBounds = super.computeDrawingBoundingBox();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/StaticLayout.java" + line="1578" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `createNoBreakSpan()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyles` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" buffer.setSpan(LineBreakConfigSpan.createNoBreakSpan()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/StringBlock.java" + line="297" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `createNoHyphenationSpan()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyles` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" buffer.setSpan(LineBreakConfigSpan.createNoHyphenationSpan()," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/StringBlock.java" + line="301" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyles` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" int lbStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/StringBlock.java" + line="387" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyles` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" int lbWordStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/StringBlock.java" + line="403" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyles` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" if (lbStyle != LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/StringBlock.java" + line="415" + column="52"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `LINE_BREAK_WORD_STYLE_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyles` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" || lbWordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/StringBlock.java" + line="416" + column="63"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `LineBreakConfigSpan()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyles` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" buffer.setSpan(new LineBreakConfigSpan(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/content/res/StringBlock.java" + line="417" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `HYPHENATION_UNSPECIFIED` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `applyStyles` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" LineBreakConfig.HYPHENATION_UNSPECIFIED))," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/res/StringBlock.java" + line="419" + column="57"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setServiceCapabilities()` is a flagged API and should be inside an `if (Flags.dataOnlyCellularService())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) to transfer requirement to caller`)" + errorLine1=" return new Builder()" + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionInfo.java" + line="957" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setTransferStatus()` is a flagged API and should be inside an `if (Flags.supportPsimToEsimConversion())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION) to transfer requirement to caller`)" + errorLine1=" return new Builder()" + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionInfo.java" + line="957" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SERVICE_CAPABILITY_DATA` is a flagged API and should be inside an `if (Flags.dataOnlyCellularService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) to transfer requirement to caller`)" + errorLine1=" public static final int SERVICE_CAPABILITY_MAX = SERVICE_CAPABILITY_DATA;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1436" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SERVICE_CAPABILITY_VOICE` is a flagged API and should be inside an `if (Flags.dataOnlyCellularService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) to transfer requirement to caller`)" + errorLine1=" serviceCapabilityToBitmask(SERVICE_CAPABILITY_VOICE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1443" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SERVICE_CAPABILITY_SMS` is a flagged API and should be inside an `if (Flags.dataOnlyCellularService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) to transfer requirement to caller`)" + errorLine1=" serviceCapabilityToBitmask(SERVICE_CAPABILITY_SMS);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1450" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SERVICE_CAPABILITY_DATA` is a flagged API and should be inside an `if (Flags.dataOnlyCellularService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) to transfer requirement to caller`)" + errorLine1=" serviceCapabilityToBitmask(SERVICE_CAPABILITY_DATA);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1457" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `addOnSubscriptionsChangedListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1696" + column="62"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `addOnSubscriptionsChangedListener()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `addOnSubscriptionsChangedListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" telephonyRegistryManager.addOnSubscriptionsChangedListener(listener," + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1699" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `removeOnSubscriptionsChangedListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1726" + column="62"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `removeOnSubscriptionsChangedListener()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `removeOnSubscriptionsChangedListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" telephonyRegistryManager.removeOnSubscriptionsChangedListener(listener);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1729" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `addOnOpportunisticSubscriptionsChangedListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1784" + column="62"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `addOnOpportunisticSubscriptionsChangedListener()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `addOnOpportunisticSubscriptionsChangedListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" telephonyRegistryManager.addOnOpportunisticSubscriptionsChangedListener(" + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1787" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `removeOnOpportunisticSubscriptionsChangedListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1808" + column="62"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `removeOnOpportunisticSubscriptionsChangedListener()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `removeOnOpportunisticSubscriptionsChangedListener` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" telephonyRegistryManager.removeOnOpportunisticSubscriptionsChangedListener(listener);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="1811" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SERVICE_CAPABILITY_VOICE` is a flagged API and should be inside an `if (Flags.dataOnlyCellularService())` check (or annotate the surrounding method `getServiceCapabilitiesSet` with `@FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) to transfer requirement to caller`)" + errorLine1=" for (int i = SERVICE_CAPABILITY_VOICE; i <= SERVICE_CAPABILITY_MAX; i++) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/SubscriptionManager.java" + line="4800" + column="22"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CREATOR` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `SurfacePackage` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" mInputTransferToken = InputTransferToken.CREATOR.createFromParcel(in);" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/SurfaceControlViewHost.java" + line="196" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `writeToParcel()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `writeToParcel` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" mInputTransferToken.writeToParcel(out, flags);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/SurfaceControlViewHost.java" + line="276" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getInputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `toString` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" return "{inputTransferToken=" + getInputTransferToken() + " remoteInterface="" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/SurfaceControlViewHost.java" + line="309" + column="45"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `InputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `SurfaceControlViewHost` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" this(context, display, hostToken == null ? null : new InputTransferToken(hostToken)," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/SurfaceControlViewHost.java" + line="352" + column="59"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `transferTouchGesture()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `transferTouchGestureToHost` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" return wm.transferTouchGesture(getInputTransferToken(), mWm.mHostInputTransferToken);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/SurfaceControlViewHost.java" + line="602" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getInputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `requestEmbeddedFocus` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" mSurfacePackage.getInputTransferToken(), gainFocus);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/SurfaceView.java" + line="2155" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `uptimeNanos()` is a flagged API and should be inside an `if (Flags.adpfGpuReportActualWorkDuration())` check (or annotate the surrounding method `uptimeMillis$ravenwood` with `@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) to transfer requirement to caller`)" + errorLine1=" return uptimeNanos() / 1_000_000;" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/SystemClock.java" + line="202" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `uptimeNanos()` is a flagged API and should be inside an `if (Flags.adpfGpuReportActualWorkDuration())` check (or annotate the surrounding method `elapsedRealtimeNanos$ravenwood` with `@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) to transfer requirement to caller`)" + errorLine1=" return uptimeNanos() + (DateUtils.HOUR_IN_MILLIS * 1_000_000);" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/SystemClock.java" + line="276" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/health/SystemHealthManager.java" + line="231" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)" + errorLine1=" @FlaggedApi("com.android.server.power.optimization.power_monitor_api")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/health/SystemHealthManager.java" + line="287" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `null()` is a flagged API and should be inside an `if (Flags.vdmPublicApis())` check (or annotate the surrounding method `setupVirtualDeviceListener` with `@FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) to transfer requirement to caller`)" + errorLine1=" mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() {" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/hardware/SystemSensorManager.java" + line="612" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.vdmPublicApis())` check (or annotate the surrounding method `setupVirtualDeviceListener` with `@FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) to transfer requirement to caller`)" + errorLine1=" mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/SystemSensorManager.java" + line="612" + column="38"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `registerVirtualDeviceListener()` is a flagged API and should be inside an `if (Flags.vdmPublicApis())` check (or annotate the surrounding method `setupVirtualDeviceListener` with `@FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) to transfer requirement to caller`)" + errorLine1=" mVdm.registerVirtualDeviceListener(mContext.getMainExecutor(), mVirtualDeviceListener);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/SystemSensorManager.java" + line="627" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `SecurityStateManager` is a flagged API and should be inside an `if (Flags.securityStateService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE) to transfer requirement to caller`)" + errorLine1=" registerService(Context.SECURITY_STATE_SERVICE, SecurityStateManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="676" + column="57"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SECURITY_STATE_SERVICE` is a flagged API and should be inside an `if (Flags.securityStateService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE) to transfer requirement to caller`)" + errorLine1=" registerService(Context.SECURITY_STATE_SERVICE, SecurityStateManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="676" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SECURITY_STATE_SERVICE` is a flagged API and should be inside an `if (Flags.securityStateService())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE) to transfer requirement to caller`)" + errorLine1=" Context.SECURITY_STATE_SERVICE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="682" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `SecurityStateManager()` is a flagged API and should be inside an `if (Flags.securityStateService())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE) to transfer requirement to caller`)" + errorLine1=" return new SecurityStateManager(service);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="684" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `TelephonyRegistryManager` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" registerService(Context.TELEPHONY_REGISTRY_SERVICE, TelephonyRegistryManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="743" + column="61"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `TelephonyRegistryManager()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" return new TelephonyRegistryManager(ctx);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="747" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `TvAdManager` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" registerService(Context.TV_AD_SERVICE, TvAdManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1019" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TV_AD_SERVICE` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" registerService(Context.TV_AD_SERVICE, TvAdManager.class," + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1019" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TV_AD_SERVICE` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" ServiceManager.getServiceOrThrow(Context.TV_AD_SERVICE);" + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1025" + column="74"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `TvAdManager()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" return new TvAdManager(service, ctx.getUserId());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1028" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `PersistentDataBlockManager` is a flagged API and should be inside an `if (Flags.frpEnforcement())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) to transfer requirement to caller`)" + errorLine1=" registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1067" + column="64"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DATA_BLOCK_SERVICE` is a flagged API and should be inside an `if (Flags.frpEnforcement())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) to transfer requirement to caller`)" + errorLine1=" registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1067" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DATA_BLOCK_SERVICE` is a flagged API and should be inside an `if (Flags.frpEnforcement())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) to transfer requirement to caller`)" + errorLine1=" IBinder b = ServiceManager.getServiceOrThrow(Context.PERSISTENT_DATA_BLOCK_SERVICE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1071" + column="70"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `PersistentDataBlockManager()` is a flagged API and should be inside an `if (Flags.frpEnforcement())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) to transfer requirement to caller`)" + errorLine1=" return new PersistentDataBlockManager(persistentDataBlockService);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1075" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `ContextualSearchManager` is a flagged API and should be inside an `if (Flags.enableService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) to transfer requirement to caller`)" + errorLine1=" registerService(Context.CONTEXTUAL_SEARCH_SERVICE, ContextualSearchManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1319" + column="60"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTEXTUAL_SEARCH_SERVICE` is a flagged API and should be inside an `if (Flags.enableService())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) to transfer requirement to caller`)" + errorLine1=" registerService(Context.CONTEXTUAL_SEARCH_SERVICE, ContextualSearchManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1319" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTEXTUAL_SEARCH_SERVICE` is a flagged API and should be inside an `if (Flags.enableService())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) to transfer requirement to caller`)" + errorLine1=" IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1324" + column="71"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `ContextualSearchManager()` is a flagged API and should be inside an `if (Flags.enableService())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) to transfer requirement to caller`)" + errorLine1=" return b == null ? null : new ContextualSearchManager();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1325" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `DeviceStateManager` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" registerService(Context.DEVICE_STATE_SERVICE, DeviceStateManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1564" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `DeviceStateManager()` is a flagged API and should be inside an `if (Flags.deviceStatePropertyApi())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_DEVICE_STATE_PROPERTY_API) to transfer requirement to caller`)" + errorLine1=" return new DeviceStateManager();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1568" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `OnDeviceIntelligenceManager` is a flagged API and should be inside an `if (Flags.enableOnDeviceIntelligence())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) to transfer requirement to caller`)" + errorLine1=" registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1638" + column="65"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `ON_DEVICE_INTELLIGENCE_SERVICE` is a flagged API and should be inside an `if (Flags.enableOnDeviceIntelligence())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) to transfer requirement to caller`)" + errorLine1=" registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1638" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `ON_DEVICE_INTELLIGENCE_SERVICE` is a flagged API and should be inside an `if (Flags.enableOnDeviceIntelligence())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) to transfer requirement to caller`)" + errorLine1=" Context.ON_DEVICE_INTELLIGENCE_SERVICE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1644" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `OnDeviceIntelligenceManager()` is a flagged API and should be inside an `if (Flags.enableOnDeviceIntelligence())` check (or annotate the surrounding method `createService` with `@FlaggedApi(Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) to transfer requirement to caller`)" + errorLine1=" return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1647" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `E2eeContactKeysManager` is a flagged API and should be inside an `if (Flags.userKeys())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_USER_KEYS) to transfer requirement to caller`)" + errorLine1=" registerService(Context.CONTACT_KEYS_SERVICE, E2eeContactKeysManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1670" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTACT_KEYS_SERVICE` is a flagged API and should be inside an `if (Flags.userKeys())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_USER_KEYS) to transfer requirement to caller`)" + errorLine1=" registerService(Context.CONTACT_KEYS_SERVICE, E2eeContactKeysManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/SystemServiceRegistry.java" + line="1670" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `addView()` is a flagged API and should be inside an `if (Flags.enableArrowIconOnHoverWhenClickable())` check (or annotate the surrounding method `addTab` with `@FlaggedApi(Flags.FLAG_ENABLE_ARROW_ICON_ON_HOVER_WHEN_CLICKABLE) to transfer requirement to caller`)" + errorLine1=" mTabWidget.addView(tabIndicator);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/widget/TabHost.java" + line="256" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `onSimultaneousCallingStateChanged` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" (SimultaneousCellularCallingSupportListener) mTelephonyCallbackWeakRef.get();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/telephony/TelephonyCallback.java" + line="2065" + column="22"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onSimultaneousCellularCallingSubscriptionsChanged()` is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `onSimultaneousCallingStateChanged` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" () -> listener.onSimultaneousCellularCallingSubscriptionsChanged(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/telephony/TelephonyCallback.java" + line="2070" + column="35"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `SatelliteManager` is a flagged API and should be inside an `if (Flags.oemEnabledSatelliteFlag())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" SatelliteManager.class," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyFrameworkInitializer.java" + line="146" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `SatelliteManager()` is a flagged API and should be inside an `if (Flags.oemEnabledSatelliteFlag())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) to transfer requirement to caller`)" + errorLine1=" ? new SatelliteManager(context) : null" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyFrameworkInitializer.java" + line="148" + column="27"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `listen` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" (TelephonyRegistryManager)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="6726" + column="18"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `listenFromListener()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `listen` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" telephonyRegistry.listenFromListener(mSubId, renounceFineLocationAccess," + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="6734" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `registerTelephonyCallback` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" mTelephonyRegistryMgr = (TelephonyRegistryManager)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="17689" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `registerTelephonyCallback()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `registerTelephonyCallback` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" mTelephonyRegistryMgr.registerTelephonyCallback(" + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="17692" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `TelephonyRegistryManager` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `unregisterTelephonyCallback` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="17717" + column="59"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `unregisterTelephonyCallback()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `unregisterTelephonyCallback` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" mTelephonyRegistryMgr.unregisterTelephonyCallback(mSubId, getOpPackageName()," + errorLine2=" ^"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="17719" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `TelephonyRegistryManager` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `registerCarrierPrivilegesCallback` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="18688" + column="59"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `addCarrierPrivilegesCallback()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `registerCarrierPrivilegesCallback` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" mTelephonyRegistryMgr.addCarrierPrivilegesCallback(logicalSlotIndex, executor, callback);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="18692" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Class `TelephonyRegistryManager` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `unregisterCarrierPrivilegesCallback` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="18708" + column="59"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `removeCarrierPrivilegesCallback()` is a flagged API and should be inside an `if (Flags.telecomResolveHiddenDependencies())` check (or annotate the surrounding method `unregisterCarrierPrivilegesCallback` with `@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) to transfer requirement to caller`)" + errorLine1=" mTelephonyRegistryMgr.removeCarrierPrivilegesCallback(callback);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/telephony/java/android/telephony/TelephonyManager.java" + line="18712" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="This is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `getEventsFromCallback` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" instanceof TelephonyCallback.SimultaneousCellularCallingSupportListener) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/telephony/TelephonyRegistryManager.java" + line="1241" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED` is a flagged API and should be inside an `if (Flags.simultaneousCallingIndications())` check (or annotate the surrounding method `getEventsFromCallback` with `@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) to transfer requirement to caller`)" + errorLine1=" TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/telephony/TelephonyRegistryManager.java" + line="1243" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `calculateRunFlag` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="342" + column="26"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `calculateRunFlag` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="342" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `calculateRunFlag` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="359" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `calculateRunFlag` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="361" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `calculateRunFlag` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="366" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `calculateRunFlag` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="368" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `resolveRunFlagForSubSequence` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="394" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `resolveRunFlagForSubSequence` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="398" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `resolveRunFlagForSubSequence` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="403" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `resolveRunFlagForSubSequence` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="407" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `resolveRunFlagForSubSequence` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="411" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `resolveRunFlagForSubSequence` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="416" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `handleText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="1345" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `handleText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="1345" + column="64"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `handleText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="1346" + column="47"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_LEFT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `handleText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="1348" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `handleText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="1350" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `handleText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="1350" + column="65"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `handleText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="1351" + column="47"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TEXT_RUN_FLAG_RIGHT_EDGE` is a flagged API and should be inside an `if (Flags.letterSpacingJustification())` check (or annotate the surrounding method `handleText` with `@FlaggedApi(Flags.FLAG_LETTER_SPACING_JUSTIFICATION) to transfer requirement to caller`)" + errorLine1=" wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextLine.java" + line="1353" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CREATOR` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" span = LineBreakConfigSpan.CREATOR.createFromParcel(p);" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/core/java/android/text/TextUtils.java" + line="1023" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getFontMetricsForLocale()` is a flagged API and should be inside an `if (Flags.fixLineHeightForLocale())` check (or annotate the surrounding method `getResolvedMinimumFontMetrics` with `@FlaggedApi(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) to transfer requirement to caller`)" + errorLine1=" mTextPaint.getFontMetricsForLocale(mLocalePreferredFontMetrics);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="10860" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setMinimumFontMetrics()` is a flagged API and should be inside an `if (Flags.fixLineHeightForLocale())` check (or annotate the surrounding method `makeNewLayout` with `@FlaggedApi(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) to transfer requirement to caller`)" + errorLine1=" StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="10965" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `makeNewLayout` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="10965" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setLineBreakConfig()` is a flagged API and should be inside an `if (Flags.noBreakNoHyphenationSpan())` check (or annotate the surrounding method `makeSingleLayout` with `@FlaggedApi(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN) to transfer requirement to caller`)" + errorLine1=" final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11029" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setMinimumFontMetrics()` is a flagged API and should be inside an `if (Flags.fixLineHeightForLocale())` check (or annotate the surrounding method `makeSingleLayout` with `@FlaggedApi(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) to transfer requirement to caller`)" + errorLine1=" final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11029" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `makeSingleLayout` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11029" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setMinimumFontMetrics()` is a flagged API and should be inside an `if (Flags.fixLineHeightForLocale())` check (or annotate the surrounding method `makeSingleLayout` with `@FlaggedApi(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) to transfer requirement to caller`)" + errorLine1=" StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11115" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `makeSingleLayout` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11115" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `computeDrawingBoundingBox()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `desired` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" max = Math.max(max, layout.computeDrawingBoundingBox().width());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11181" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDrawingBoundingBox()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `onMeasure` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" RectF bbox = boring.getDrawingBoundingBox();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11275" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setMinimumFontMetrics()` is a flagged API and should be inside an `if (Flags.fixLineHeightForLocale())` check (or annotate the surrounding method `suggestedSizeFitsInSpace` with `@FlaggedApi(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) to transfer requirement to caller`)" + errorLine1=" layoutBuilder.setAlignment(getLayoutAlignment())" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11502" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setUseBoundsForWidth()` is a flagged API and should be inside an `if (Flags.useBoundsForWidth())` check (or annotate the surrounding method `suggestedSizeFitsInSpace` with `@FlaggedApi(Flags.FLAG_USE_BOUNDS_FOR_WIDTH) to transfer requirement to caller`)" + errorLine1=" layoutBuilder.setAlignment(getLayoutAlignment())" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/widget/TextView.java" + line="11502" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONFIG_FORCE_ANALOG_FM` is a flagged API and should be inside an `if (Flags.hdRadioImproved())` check (or annotate the surrounding method `convertForceAnalogConfigFlag` with `@FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) to transfer requirement to caller`)" + errorLine1=" && mTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/radio/TunerAdapter.java" + line="427" + column="62"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `notifyTvInputSessionData()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `run` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" mSession.getAdSession().notifyTvInputSessionData(type, data);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/TvInputManager.java" + line="1498" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onStopPlayback()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `stopPlayback` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" onStopPlayback(mode);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/TvInputService.java" + line="2113" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onResumePlayback()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `resumePlayback` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" onResumePlayback();" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/TvInputService.java" + line="2120" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onTvAdSessionData()` is a flagged API and should be inside an `if (Flags.enableAdServiceFw())` check (or annotate the surrounding method `notifyTvAdSessionData` with `@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) to transfer requirement to caller`)" + errorLine1=" onTvAdSessionData(type, data);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/TvInputService.java" + line="2218" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onCertificate()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `sendCertificate` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" onCertificate(host, port, cert);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/interactive/TvInteractiveAppService.java" + line="1824" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onVideoFreezeUpdated()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `notifyVideoFreezeUpdated` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" onVideoFreezeUpdated(isFrozen);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/interactive/TvInteractiveAppService.java" + line="1889" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onSelectedTrackInfo()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `sendSelectedTrackInfo` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" onSelectedTrackInfo(tracks);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/interactive/TvInteractiveAppService.java" + line="1945" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onRequestSelectedTrackInfo()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `onRequestSelectedTrackInfo` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" mCallback.onRequestSelectedTrackInfo(mIAppServiceId);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/interactive/TvInteractiveAppView.java" + line="1822" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onVideoFreezeUpdated()` is a flagged API and should be inside an `if (Flags.tiafVApis())` check (or annotate the surrounding method `onVideoFreezeUpdated` with `@FlaggedApi(Flags.FLAG_TIAF_V_APIS) to transfer requirement to caller`)" + errorLine1=" mCallback.onVideoFreezeUpdated(mInputId, isFrozen);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/media/java/android/media/tv/TvView.java" + line="1779" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `convertSpToDp()` is a flagged API and should be inside an `if (Flags.fontScaleConverterPublic())` check (or annotate the surrounding method `applyDimension` with `@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) to transfer requirement to caller`)" + errorLine1=" metrics.fontScaleConverter.convertSpToDp(value)," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/util/TypedValue.java" + line="433" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `convertDpToSp()` is a flagged API and should be inside an `if (Flags.fontScaleConverterPublic())` check (or annotate the surrounding method `deriveDimension` with `@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) to transfer requirement to caller`)" + errorLine1=" return metrics.fontScaleConverter.convertDpToSp(dpValue);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/util/TypedValue.java" + line="479" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `grantRuntimePermission` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/UiAutomationConnection.java" + line="367" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `PERSISTENT_DEVICE_ID_DEFAULT` is a flagged API and should be inside an `if (Flags.persistentDeviceIdApi())` check (or annotate the surrounding method `revokeRuntimePermission` with `@FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) to transfer requirement to caller`)" + errorLine1=" VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId, null);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/app/UiAutomationConnection.java" + line="387" + column="42"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isResumed()` is a flagged API and should be inside an `if (Flags.enableNfcMainline())` check (or annotate the surrounding method `dump` with `@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) to transfer requirement to caller`)" + errorLine1=" pw.print(pfx); pw.print("resumed: "); pw.println(mActivity.isResumed());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/translation/UiTranslationController.java" + line="219" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `COMPLIANCE_WARNING_INPUT_POWER_LIMITED` is a flagged API and should be inside an `if (Flags.enableUsbDataComplianceWarning())` check (or annotate the surrounding method `complianceWarningsToString` with `@FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING) to transfer requirement to caller`)" + errorLine1=" case UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/usb/UsbPort.java" + line="813" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `COMPLIANCE_WARNING_MISSING_DATA_LINES` is a flagged API and should be inside an `if (Flags.enableUsbDataComplianceWarning())` check (or annotate the surrounding method `complianceWarningsToString` with `@FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING) to transfer requirement to caller`)" + errorLine1=" case UsbPortStatus.COMPLIANCE_WARNING_MISSING_DATA_LINES:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/usb/UsbPort.java" + line="816" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `COMPLIANCE_WARNING_ENUMERATION_FAIL` is a flagged API and should be inside an `if (Flags.enableUsbDataComplianceWarning())` check (or annotate the surrounding method `complianceWarningsToString` with `@FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING) to transfer requirement to caller`)" + errorLine1=" case UsbPortStatus.COMPLIANCE_WARNING_ENUMERATION_FAIL:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/usb/UsbPort.java" + line="819" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `COMPLIANCE_WARNING_FLAKY_CONNECTION` is a flagged API and should be inside an `if (Flags.enableUsbDataComplianceWarning())` check (or annotate the surrounding method `complianceWarningsToString` with `@FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING) to transfer requirement to caller`)" + errorLine1=" case UsbPortStatus.COMPLIANCE_WARNING_FLAKY_CONNECTION:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/usb/UsbPort.java" + line="822" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `COMPLIANCE_WARNING_UNRELIABLE_IO` is a flagged API and should be inside an `if (Flags.enableUsbDataComplianceWarning())` check (or annotate the surrounding method `complianceWarningsToString` with `@FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING) to transfer requirement to caller`)" + errorLine1=" case UsbPortStatus.COMPLIANCE_WARNING_UNRELIABLE_IO:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/usb/UsbPort.java" + line="825" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `USER_TYPE_PROFILE_MANAGED` is a flagged API and should be inside an `if (Flags.allowPrivateProfile())` check (or annotate the surrounding method `getDefaultUserType` with `@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) to transfer requirement to caller`)" + errorLine1=" case FLAG_MANAGED_PROFILE: return UserManager.USER_TYPE_PROFILE_MANAGED;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/content/pm/UserInfo.java" + line="350" + column="59"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `USER_TYPE_PROFILE_MANAGED` is a flagged API and should be inside an `if (Flags.allowPrivateProfile())` check (or annotate the surrounding method `isUserTypeManagedProfile` with `@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) to transfer requirement to caller`)" + errorLine1=" return USER_TYPE_PROFILE_MANAGED.equals(userType);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/UserManager.java" + line="3063" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `USER_TYPE_PROFILE_CLONE` is a flagged API and should be inside an `if (Flags.allowPrivateProfile())` check (or annotate the surrounding method `isUserTypeCloneProfile` with `@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) to transfer requirement to caller`)" + errorLine1=" return USER_TYPE_PROFILE_CLONE.equals(userType);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/UserManager.java" + line="3100" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `USER_TYPE_PROFILE_PRIVATE` is a flagged API and should be inside an `if (Flags.allowPrivateProfile())` check (or annotate the surrounding method `isUserTypePrivateProfile` with `@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) to transfer requirement to caller`)" + errorLine1=" return USER_TYPE_PROFILE_PRIVATE.equals(userType);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/os/UserManager.java" + line="3121" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTENT_SENSITIVITY_AUTO` is a flagged API and should be inside an `if (Flags.sensitiveContentAppProtectionApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) to transfer requirement to caller`)" + errorLine1=" (CONTENT_SENSITIVITY_AUTO | CONTENT_SENSITIVITY_SENSITIVE" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="3929" + column="14"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTENT_SENSITIVITY_SENSITIVE` is a flagged API and should be inside an `if (Flags.sensitiveContentAppProtectionApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) to transfer requirement to caller`)" + errorLine1=" (CONTENT_SENSITIVITY_AUTO | CONTENT_SENSITIVITY_SENSITIVE" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="3929" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTENT_SENSITIVITY_NOT_SENSITIVE` is a flagged API and should be inside an `if (Flags.sensitiveContentAppProtectionApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) to transfer requirement to caller`)" + errorLine1=" | CONTENT_SENSITIVITY_NOT_SENSITIVE) << PFLAG4_CONTENT_SENSITIVITY_SHIFT;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="3930" + column="23"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REQUESTED_FRAME_RATE_CATEGORY_DEFAULT` is a flagged API and should be inside an `if (Flags.toolkitSetFrameRateReadOnly())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) to transfer requirement to caller`)" + errorLine1=" private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="5784" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTENT_SENSITIVITY_AUTO` is a flagged API and should be inside an `if (Flags.sensitiveContentAppProtectionApi())` check (or annotate the surrounding method `View` with `@FlaggedApi(Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) to transfer requirement to caller`)" + errorLine1=" setContentSensitivity(a.getInt(attr, CONTENT_SENSITIVITY_AUTO));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="6528" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setContentSensitivity()` is a flagged API and should be inside an `if (Flags.sensitiveContentAppProtectionApi())` check (or annotate the surrounding method `View` with `@FlaggedApi(Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) to transfer requirement to caller`)" + errorLine1=" setContentSensitivity(a.getInt(attr, CONTENT_SENSITIVITY_AUTO));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="6528" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setPendingCredentialRequest()` is a flagged API and should be inside an `if (Flags.autofillCredmanDevIntegration())` check (or annotate the surrounding method `onProvideStructure` with `@FlaggedApi(Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) to transfer requirement to caller`)" + errorLine1=" structure.setPendingCredentialRequest(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="9665" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPendingCredentialCallback()` is a flagged API and should be inside an `if (Flags.autofillCredmanDevIntegration())` check (or annotate the surrounding method `onGetCredentialResponse` with `@FlaggedApi(Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) to transfer requirement to caller`)" + errorLine1=" if (getPendingCredentialCallback() == null) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="10070" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPendingCredentialCallback()` is a flagged API and should be inside an `if (Flags.autofillCredmanDevIntegration())` check (or annotate the surrounding method `onGetCredentialResponse` with `@FlaggedApi(Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) to transfer requirement to caller`)" + errorLine1=" getPendingCredentialCallback().onResult(response);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="10074" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPendingCredentialCallback()` is a flagged API and should be inside an `if (Flags.autofillCredmanDevIntegration())` check (or annotate the surrounding method `onGetCredentialException` with `@FlaggedApi(Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) to transfer requirement to caller`)" + errorLine1=" if (getPendingCredentialCallback() == null) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="10081" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPendingCredentialCallback()` is a flagged API and should be inside an `if (Flags.autofillCredmanDevIntegration())` check (or annotate the surrounding method `onGetCredentialException` with `@FlaggedApi(Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) to transfer requirement to caller`)" + errorLine1=" getPendingCredentialCallback().onError(new GetCredentialException(errorType, errorMsg));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="10085" + column="9"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isContentSensitive()` is a flagged API and should be inside an `if (Flags.sensitiveContentAppProtectionApi())` check (or annotate the surrounding method `updateSensitiveViewsCountIfNeeded` with `@FlaggedApi(Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) to transfer requirement to caller`)" + errorLine1=" if (appeared && isContentSensitive()) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="10597" + column="25"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setPendingCredentialRequest()` is a flagged API and should be inside an `if (Flags.autofillCredmanDevIntegration())` check (or annotate the surrounding method `populateVirtualStructure` with `@FlaggedApi(Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) to transfer requirement to caller`)" + errorLine1=" structure.setPendingCredentialRequest(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="11106" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `CONTENT_SENSITIVITY_AUTO` is a flagged API and should be inside an `if (Flags.sensitiveContentAppProtectionApi())` check (or annotate the surrounding method `setAutofillHints` with `@FlaggedApi(Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) to transfer requirement to caller`)" + errorLine1=" if (getContentSensitivity() == CONTENT_SENSITIVITY_AUTO) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="13689" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getContentSensitivity()` is a flagged API and should be inside an `if (Flags.sensitiveContentAppProtectionApi())` check (or annotate the surrounding method `setAutofillHints` with `@FlaggedApi(Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) to transfer requirement to caller`)" + errorLine1=" if (getContentSensitivity() == CONTENT_SENSITIVITY_AUTO) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="13689" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `DRAG_FLAG_GLOBAL_SAME_APPLICATION` is a flagged API and should be inside an `if (Flags.delegateUnhandledDrags())` check (or annotate the surrounding method `startDragAndDrop` with `@FlaggedApi(Flags.FLAG_DELEGATE_UNHANDLED_DRAGS) to transfer requirement to caller`)" + errorLine1=" if ((flags & DRAG_FLAG_GLOBAL) != 0 && ((flags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="29019" + column="58"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getIntentSender()` is a flagged API and should be inside an `if (Flags.delegateUnhandledDrags())` check (or annotate the surrounding method `hasActivityPendingIntents` with `@FlaggedApi(Flags.FLAG_DELEGATE_UNHANDLED_DRAGS) to transfer requirement to caller`)" + errorLine1=" if (item.getIntentSender() != null) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="29187" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getIntentSender()` is a flagged API and should be inside an `if (Flags.delegateUnhandledDrags())` check (or annotate the surrounding method `hasActivityPendingIntents` with `@FlaggedApi(Flags.FLAG_DELEGATE_UNHANDLED_DRAGS) to transfer requirement to caller`)" + errorLine1=" final PendingIntent pi = new PendingIntent(item.getIntentSender().getTarget());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="29188" + column="60"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getIntentSender()` is a flagged API and should be inside an `if (Flags.delegateUnhandledDrags())` check (or annotate the surrounding method `cleanUpPendingIntents` with `@FlaggedApi(Flags.FLAG_DELEGATE_UNHANDLED_DRAGS) to transfer requirement to caller`)" + errorLine1=" if (item.getIntentSender() != null) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="29205" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getIntentSender()` is a flagged API and should be inside an `if (Flags.delegateUnhandledDrags())` check (or annotate the surrounding method `cleanUpPendingIntents` with `@FlaggedApi(Flags.FLAG_DELEGATE_UNHANDLED_DRAGS) to transfer requirement to caller`)" + errorLine1=" final PendingIntent pi = new PendingIntent(item.getIntentSender().getTarget());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="29206" + column="60"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE` is a flagged API and should be inside an `if (Flags.toolkitSetFrameRateReadOnly())` check (or annotate the surrounding method `votePreferredFrameRate` with `@FlaggedApi(Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) to transfer requirement to caller`)" + errorLine1=" case (int) REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE ->" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="33991" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REQUESTED_FRAME_RATE_CATEGORY_LOW` is a flagged API and should be inside an `if (Flags.toolkitSetFrameRateReadOnly())` check (or annotate the surrounding method `votePreferredFrameRate` with `@FlaggedApi(Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) to transfer requirement to caller`)" + errorLine1=" case (int) REQUESTED_FRAME_RATE_CATEGORY_LOW ->" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="33994" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REQUESTED_FRAME_RATE_CATEGORY_NORMAL` is a flagged API and should be inside an `if (Flags.toolkitSetFrameRateReadOnly())` check (or annotate the surrounding method `votePreferredFrameRate` with `@FlaggedApi(Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) to transfer requirement to caller`)" + errorLine1=" case (int) REQUESTED_FRAME_RATE_CATEGORY_NORMAL ->" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="33997" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REQUESTED_FRAME_RATE_CATEGORY_HIGH` is a flagged API and should be inside an `if (Flags.toolkitSetFrameRateReadOnly())` check (or annotate the surrounding method `votePreferredFrameRate` with `@FlaggedApi(Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) to transfer requirement to caller`)" + errorLine1=" case (int) REQUESTED_FRAME_RATE_CATEGORY_HIGH ->" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/View.java" + line="34000" + column="32"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDesiredHdrHeadroom()` is a flagged API and should be inside an `if (Flags.limitedHdr())` check (or annotate the surrounding method `enableHardwareAcceleration` with `@FlaggedApi(Flags.FLAG_LIMITED_HDR) to transfer requirement to caller`)" + errorLine1=" updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ViewRootImpl.java" + line="2022" + column="63"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getDesiredHdrHeadroom()` is a flagged API and should be inside an `if (Flags.limitedHdr())` check (or annotate the surrounding method `performTraversals` with `@FlaggedApi(Flags.FLAG_LIMITED_HDR) to transfer requirement to caller`)" + errorLine1=" updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ViewRootImpl.java" + line="3748" + column="60"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getFrameTimeNanos()` is a flagged API and should be inside an `if (Flags.expectedPresentationTimeApi())` check (or annotate the surrounding method `draw` with `@FlaggedApi(Flags.FLAG_EXPECTED_PRESENTATION_TIME_API) to transfer requirement to caller`)" + errorLine1=" mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ViewRootImpl.java" + line="5597" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `uptimeNanos()` is a flagged API and should be inside an `if (Flags.adpfGpuReportActualWorkDuration())` check (or annotate the surrounding method `draw` with `@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) to transfer requirement to caller`)" + errorLine1=" long timeNs = SystemClock.uptimeNanos();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ViewRootImpl.java" + line="5658" + column="31"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getFrameTimeNanos()` is a flagged API and should be inside an `if (Flags.expectedPresentationTimeApi())` check (or annotate the surrounding method `run` with `@FlaggedApi(Flags.FLAG_EXPECTED_PRESENTATION_TIME_API) to transfer requirement to caller`)" + errorLine1=" if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ViewRootImpl.java" + line="10449" + column="43"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `InputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `getInputTransferToken` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" return new InputTransferToken(inputToken);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ViewRootImpl.java" + line="11736" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getFrameRateBoostOnTouchEnabled()` is a flagged API and should be inside an `if (Flags.toolkitSetFrameRateReadOnly())` check (or annotate the surrounding method `getFrameRateBoostOnTouchEnabled` with `@FlaggedApi(Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) to transfer requirement to caller`)" + errorLine1=" return mWindowAttributes.getFrameRateBoostOnTouchEnabled();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ViewRootImpl.java" + line="13161" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `isFrameRatePowerSavingsBalanced()` is a flagged API and should be inside an `if (Flags.toolkitSetFrameRateReadOnly())` check (or annotate the surrounding method `isFrameRatePowerSavingsBalanced` with `@FlaggedApi(Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) to transfer requirement to caller`)" + errorLine1=" return mWindowAttributes.isFrameRatePowerSavingsBalanced();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/ViewRootImpl.java" + line="13193" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `POLICY_TYPE_CAMERA` is a flagged API and should be inside an `if (Flags.virtualCamera())` check (or annotate the surrounding method `hasCustomCameraSupport` with `@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) to transfer requirement to caller`)" + errorLine1=" return mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA) == DEVICE_POLICY_CUSTOM;" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/companion/virtual/VirtualDevice.java" + line="200" + column="51"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `VirtualStylus()` is a flagged API and should be inside an `if (Flags.virtualStylus())` check (or annotate the surrounding method `createVirtualStylus` with `@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) to transfer requirement to caller`)" + errorLine1=" return new VirtualStylus(config, mVirtualDevice, token);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/companion/virtual/VirtualDeviceInternal.java" + line="332" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `VirtualCamera()` is a flagged API and should be inside an `if (Flags.virtualCamera())` check (or annotate the surrounding method `createVirtualCamera` with `@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) to transfer requirement to caller`)" + errorLine1=" return new VirtualCamera(mVirtualDevice, mVirtualDevice.getVirtualCameraId(config)," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/companion/virtual/VirtualDeviceInternal.java" + line="381" + column="20"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onVirtualDeviceCreated()` is a flagged API and should be inside an `if (Flags.vdmPublicApis())` check (or annotate the surrounding method `onVirtualDeviceCreated` with `@FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) to transfer requirement to caller`)" + errorLine1=" mExecutor.execute(() -> mListener.onVirtualDeviceCreated(deviceId));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/companion/virtual/VirtualDeviceManager.java" + line="1225" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onVirtualDeviceClosed()` is a flagged API and should be inside an `if (Flags.vdmPublicApis())` check (or annotate the surrounding method `onVirtualDeviceClosed` with `@FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) to transfer requirement to caller`)" + errorLine1=" mExecutor.execute(() -> mListener.onVirtualDeviceClosed(deviceId));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/companion/virtual/VirtualDeviceManager.java" + line="1235" + column="41"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `POLICY_TYPE_CLIPBOARD` is a flagged API and should be inside an `if (Flags.crossDeviceClipboard())` check (or annotate the surrounding method `build` with `@FlaggedApi(Flags.FLAG_CROSS_DEVICE_CLIPBOARD) to transfer requirement to caller`)" + errorLine1=" mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/companion/virtual/VirtualDeviceParams.java" + line="1170" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `POLICY_TYPE_CAMERA` is a flagged API and should be inside an `if (Flags.virtualCamera())` check (or annotate the surrounding method `build` with `@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) to transfer requirement to caller`)" + errorLine1=" mDevicePolicies.delete(POLICY_TYPE_CAMERA);" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/companion/virtual/VirtualDeviceParams.java" + line="1174" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onSecureConnectionProvided()` is a flagged API and should be inside an `if (Flags.enableProvideWearableConnectionApi())` check (or annotate the surrounding method `provideSecureConnection` with `@FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API) to transfer requirement to caller`)" + errorLine1=" WearableSensingService.this.onSecureConnectionProvided(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="142" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onDataRequestObserverRegistered()` is a flagged API and should be inside an `if (Flags.enableDataRequestObserverApi())` check (or annotate the surrounding method `registerDataRequestObserver` with `@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) to transfer requirement to caller`)" + errorLine1=" WearableSensingService.this.onDataRequestObserverRegistered(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="193" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onDataRequestObserverUnregistered()` is a flagged API and should be inside an `if (Flags.enableDataRequestObserverApi())` check (or annotate the surrounding method `unregisterDataRequestObserver` with `@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) to transfer requirement to caller`)" + errorLine1=" WearableSensingService.this.onDataRequestObserverUnregistered(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="217" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onStartHotwordRecognition()` is a flagged API and should be inside an `if (Flags.enableHotwordWearableSensingApi())` check (or annotate the surrounding method `startHotwordRecognition` with `@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) to transfer requirement to caller`)" + errorLine1=" WearableSensingService.this.onStartHotwordRecognition(" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="237" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onStopHotwordRecognition()` is a flagged API and should be inside an `if (Flags.enableHotwordWearableSensingApi())` check (or annotate the surrounding method `stopHotwordRecognition` with `@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) to transfer requirement to caller`)" + errorLine1=" WearableSensingService.this.onStopHotwordRecognition(statusConsumer);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="250" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onValidatedByHotwordDetectionService()` is a flagged API and should be inside an `if (Flags.enableHotwordWearableSensingApi())` check (or annotate the surrounding method `onValidatedByHotwordDetectionService` with `@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) to transfer requirement to caller`)" + errorLine1=" WearableSensingService.this.onValidatedByHotwordDetectionService();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="256" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onStopHotwordAudioStream()` is a flagged API and should be inside an `if (Flags.enableHotwordWearableSensingApi())` check (or annotate the surrounding method `stopActiveHotwordAudio` with `@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) to transfer requirement to caller`)" + errorLine1=" WearableSensingService.this.onStopHotwordAudioStream();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="262" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `STATUS_UNSUPPORTED_OPERATION` is a flagged API and should be inside an `if (Flags.enableUnsupportedOperationStatusCode())` check (or annotate the surrounding method `onSecureConnectionProvided` with `@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE) to transfer requirement to caller`)" + errorLine1=" statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="361" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `STATUS_UNSUPPORTED_OPERATION` is a flagged API and should be inside an `if (Flags.enableUnsupportedOperationStatusCode())` check (or annotate the surrounding method `onDataRequestObserverRegistered` with `@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE) to transfer requirement to caller`)" + errorLine1=" statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="429" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `STATUS_UNSUPPORTED_OPERATION` is a flagged API and should be inside an `if (Flags.enableUnsupportedOperationStatusCode())` check (or annotate the surrounding method `onDataRequestObserverUnregistered` with `@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE) to transfer requirement to caller`)" + errorLine1=" statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="457" + column="54"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REQUEST_BUNDLE_KEY` is a flagged API and should be inside an `if (Flags.enableDataRequestObserverApi())` check (or annotate the surrounding method `createDataRequester` with `@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) to transfer requirement to caller`)" + errorLine1=" bundle.putParcelable(WearableSensingDataRequest.REQUEST_BUNDLE_KEY, request);" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="673" + column="61"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `REQUEST_STATUS_CALLBACK_BUNDLE_KEY` is a flagged API and should be inside an `if (Flags.enableDataRequestObserverApi())` check (or annotate the surrounding method `createDataRequester` with `@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) to transfer requirement to caller`)" + errorLine1=" WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/wearable/WearableSensingService.java" + line="682" + column="48"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `status` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `loadWebViewNativeLibraryFromPackage` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" if (response.status != LIBLOAD_SUCCESS" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="308" + column="22"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `status` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `loadWebViewNativeLibraryFromPackage` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="309" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `status` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `loadWebViewNativeLibraryFromPackage` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" return response.status;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="310" + column="29"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `packageInfo` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `loadWebViewNativeLibraryFromPackage` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" if (!response.packageInfo.packageName.equals(packageName)) {" + errorLine2=" ~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="312" + column="23"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `status` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `loadWebViewNativeLibraryFromPackage` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="330" + column="63"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `status` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `getWebViewContextAndSetProvider` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" if (response.status != LIBLOAD_SUCCESS" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="459" + column="26"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `status` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `getWebViewContextAndSetProvider` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="460" + column="33"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `status` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `getWebViewContextAndSetProvider` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" + getWebViewPreparationErrorReason(response.status));" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="462" + column="69"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `packageInfo` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `getWebViewContextAndSetProvider` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" response.packageInfo.packageName);" + errorLine2=" ~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="469" + column="34"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `packageInfo` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `getWebViewContextAndSetProvider` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" response.packageInfo.packageName," + errorLine2=" ~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="479" + column="30"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `packageInfo` is a flagged API and should be inside an `if (Flags.updateServiceIpcWrapper())` check (or annotate the surrounding method `getWebViewContextAndSetProvider` with `@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER) to transfer requirement to caller`)" + errorLine1=" verifyPackageInfo(response.packageInfo, newPackageInfo);" + errorLine2=" ~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/webkit/WebViewFactory.java" + line="510" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `InputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `registerBatchedSurfaceControlInputReceiver` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" InputTransferToken inputTransferToken = new InputTransferToken();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/WindowManagerGlobal.java" + line="874" + column="49"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onInputEvent()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `onInputEvent` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" boolean handled = receiver.onInputEvent(event);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/WindowManagerGlobal.java" + line="885" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `InputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `registerUnbatchedSurfaceControlInputReceiver` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" InputTransferToken inputTransferToken = new InputTransferToken();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/WindowManagerGlobal.java" + line="897" + column="49"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onInputEvent()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `onInputEvent` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" boolean handled = receiver.onInputEvent(event);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/WindowManagerGlobal.java" + line="907" + column="55"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `SCREEN_RECORDING_STATE_NOT_VISIBLE` is a flagged API and should be inside an `if (Flags.screenRecordingCallbacks())` check (or annotate the surrounding method `addScreenRecordingCallback` with `@FlaggedApi(Flags.FLAG_SCREEN_RECORDING_CALLBACKS) to transfer requirement to caller`)" + errorLine1=" return SCREEN_RECORDING_STATE_NOT_VISIBLE;" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/WindowManagerImpl.java" + line="594" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `InputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" private final InputTransferToken mInputTransferToken = new InputTransferToken();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/WindowlessWindowManager.java" + line="94" + column="60"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `InputTransferToken()` is a flagged API and should be inside an `if (Flags.surfaceControlInputReceiver())` check (or annotate the surrounding method `addToDisplay` with `@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) to transfer requirement to caller`)" + errorLine1=" state.mInputTransferToken = new InputTransferToken();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/WindowlessWindowManager.java" + line="214" + column="45"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `compressToJpegR()` is a flagged API and should be inside an `if (Flags.yuvImageCompressToUltraHdr())` check (or annotate the surrounding method `compressToJpegR` with `@FlaggedApi(Flags.FLAG_YUV_IMAGE_COMPRESS_TO_ULTRA_HDR) to transfer requirement to caller`)" + errorLine1=" return compressToJpegR(sdr, quality, stream, emptyExif);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/graphics/java/android/graphics/YuvImage.java" + line="276" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `allowPriorityChannels()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `getDefaultZenPolicy` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" ZenPolicy policy = new ZenPolicy.Builder()" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/service/notification/ZenModeConfig.java" + line="379" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_OTHER` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `ensureManualZenRule` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" newRule.type = AutomaticZenRule.TYPE_OTHER;" + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenModeConfig.java" + line="435" + column="45"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `zenDeviceEffects` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `writeRuleXml` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if (Flags.modesApi() && rule.zenDeviceEffects != null) {" + errorLine2=" ~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenModeConfig.java" + line="1206" + column="38"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `policyState()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `toNotificationPolicy` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" state = Policy.policyState(areChannelsBypassingDnd," + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/service/notification/ZenModeConfig.java" + line="1842" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPriorityChannelsAllowed()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `toNotificationPolicy` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" manualRule.zenPolicy.getPriorityChannelsAllowed() != STATE_DISALLOW);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenModeConfig.java" + line="1843" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `TYPE_UNKNOWN` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" public int type = AutomaticZenRule.TYPE_UNKNOWN;" + errorLine2=" ~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenModeConfig.java" + line="2482" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPriorityChannelsAllowed()` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `areAllPriorityOnlyRingerSoundsMuted` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" && !(config.areChannelsBypassingDnd && policy.getPriorityChannelsAllowed()" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenModeConfig.java" + line="2835" + column="60"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `mAllowChannels` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `getAllowedChannels` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" return mAllowChannels;" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="576" + column="16"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `mAllowChannels` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `allowChannels` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" mZenPolicy.mAllowChannels = channelType;" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1019" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_MESSAGES` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_MESSAGES) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1095" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_CALLS` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_CALLS) != 0) {" + errorLine2=" ~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1098" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_CONVERSATIONS` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_CONVERSATIONS) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1101" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_ALLOW_CHANNELS` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_ALLOW_CHANNELS) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1104" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_PRIORITY_CATEGORY_REMINDERS` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_PRIORITY_CATEGORY_REMINDERS) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1107" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_PRIORITY_CATEGORY_EVENTS` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_PRIORITY_CATEGORY_EVENTS) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1110" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1113" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_PRIORITY_CATEGORY_ALARMS` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_PRIORITY_CATEGORY_ALARMS) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1116" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_PRIORITY_CATEGORY_MEDIA` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_PRIORITY_CATEGORY_MEDIA) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1119" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_PRIORITY_CATEGORY_SYSTEM` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_PRIORITY_CATEGORY_SYSTEM) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1122" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1125" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_VISUAL_EFFECT_LIGHTS` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_VISUAL_EFFECT_LIGHTS) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1128" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_VISUAL_EFFECT_PEEK` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_VISUAL_EFFECT_PEEK) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1131" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_VISUAL_EFFECT_STATUS_BAR` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_VISUAL_EFFECT_STATUS_BAR) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1134" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_VISUAL_EFFECT_BADGE` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_VISUAL_EFFECT_BADGE) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1137" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_VISUAL_EFFECT_AMBIENT` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_VISUAL_EFFECT_AMBIENT) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1140" + column="24"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FIELD_VISUAL_EFFECT_NOTIFICATION_LIST` is a flagged API and should be inside an `if (Flags.modesApi())` check (or annotate the surrounding method `fieldsToString` with `@FlaggedApi(Flags.FLAG_MODES_API) to transfer requirement to caller`)" + errorLine1=" if ((bitmask & FIELD_VISUAL_EFFECT_NOTIFICATION_LIST) != 0) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/notification/ZenPolicy.java" + line="1143" + column="24"/> + </issue> + </issues>
\ No newline at end of file diff --git a/location/Android.bp b/location/Android.bp index 5ba35ac3328a..e864689bbdee 100644 --- a/location/Android.bp +++ b/location/Android.bp @@ -39,6 +39,9 @@ java_sdk_library { "frameworks/base/core/java", ], }, + lint: { + baseline_filename: "lint-baseline.xml", + }, } platform_compat_config { diff --git a/location/lint-baseline.xml b/location/lint-baseline.xml new file mode 100644 index 000000000000..a5a2e259b8ac --- /dev/null +++ b/location/lint-baseline.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08"> + + <issue + id="FlaggedApi" + message="Method `Builder()` is a flagged API and should be inside an `if (Flags.newGeocoder())` check (or annotate the surrounding method `getFromLocation` with `@FlaggedApi(Flags.FLAG_NEW_GEOCODER) to transfer requirement to caller`)" + errorLine1=" new ReverseGeocodeRequest.Builder(" + errorLine2=" ^"> + <location + file="frameworks/base/location/java/android/location/Geocoder.java" + line="170" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setCallingAttributionTag()` is a flagged API and should be inside an `if (Flags.newGeocoder())` check (or annotate the surrounding method `getFromLocation` with `@FlaggedApi(Flags.FLAG_NEW_GEOCODER) to transfer requirement to caller`)" + errorLine1=" b.setCallingAttributionTag(mContext.getAttributionTag());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/location/java/android/location/Geocoder.java" + line="178" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `build()` is a flagged API and should be inside an `if (Flags.newGeocoder())` check (or annotate the surrounding method `getFromLocation` with `@FlaggedApi(Flags.FLAG_NEW_GEOCODER) to transfer requirement to caller`)" + errorLine1=" mService.reverseGeocode(b.build(), new GeocodeCallbackImpl(listener));" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/location/java/android/location/Geocoder.java" + line="181" + column="37"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `Builder()` is a flagged API and should be inside an `if (Flags.newGeocoder())` check (or annotate the surrounding method `getFromLocationName` with `@FlaggedApi(Flags.FLAG_NEW_GEOCODER) to transfer requirement to caller`)" + errorLine1=" new ForwardGeocodeRequest.Builder(" + errorLine2=" ^"> + <location + file="frameworks/base/location/java/android/location/Geocoder.java" + line="322" + column="17"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `setCallingAttributionTag()` is a flagged API and should be inside an `if (Flags.newGeocoder())` check (or annotate the surrounding method `getFromLocationName` with `@FlaggedApi(Flags.FLAG_NEW_GEOCODER) to transfer requirement to caller`)" + errorLine1=" b.setCallingAttributionTag(mContext.getAttributionTag());" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/location/java/android/location/Geocoder.java" + line="333" + column="13"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `build()` is a flagged API and should be inside an `if (Flags.newGeocoder())` check (or annotate the surrounding method `getFromLocationName` with `@FlaggedApi(Flags.FLAG_NEW_GEOCODER) to transfer requirement to caller`)" + errorLine1=" mService.forwardGeocode(b.build(), new GeocodeCallbackImpl(listener));" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/location/java/android/location/Geocoder.java" + line="336" + column="37"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getWorkSource()` is a flagged API and should be inside an `if (Flags.gnssApiMeasurementRequestWorkSource())` check (or annotate the surrounding method `Builder` with `@FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) to transfer requirement to caller`)" + errorLine1=" mWorkSource = request.getWorkSource();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/location/java/android/location/GnssMeasurementRequest.java" + line="234" + column="27"/> + </issue> + +</issues> diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 386a606c61c6..e134c23dfbca 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4069,8 +4069,10 @@ public class AudioManager { private boolean delegateSoundEffectToVdm(@SystemSoundEffect int effectType) { if (hasCustomPolicyVirtualDeviceContext()) { VirtualDeviceManager vdm = getVirtualDeviceManager(); - vdm.playSoundEffect(mOriginalContextDeviceId, effectType); - return true; + if (vdm != null) { + vdm.playSoundEffect(mOriginalContextDeviceId, effectType); + return true; + } } return false; } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 0589c0f12ab6..e048d5c56cfe 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -26,6 +26,7 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.net.Uri; import android.os.Bundle; @@ -813,6 +814,34 @@ public final class MediaRoute2Info implements Parcelable { || mAllowedPackages.contains(packageName); } + /** + * Returns whether this route's type can only be published by the system route provider. + * + * @see #isSystemRoute() + * @hide + */ + // The default case catches all other types. + @SuppressLint("SwitchIntDef") + public boolean isSystemRouteType() { + return switch (mType) { + case TYPE_BUILTIN_SPEAKER, + TYPE_BLUETOOTH_A2DP, + TYPE_DOCK, + TYPE_BLE_HEADSET, + TYPE_HEARING_AID, + TYPE_HDMI, + TYPE_HDMI_ARC, + TYPE_HDMI_EARC, + TYPE_USB_ACCESSORY, + TYPE_USB_DEVICE, + TYPE_USB_HEADSET, + TYPE_WIRED_HEADPHONES, + TYPE_WIRED_HEADSET -> + true; + default -> false; + }; + } + /** Returns the route suitability status. */ @SuitabilityStatus @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index cce3d4fc67d5..a14f1fd15f27 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -475,9 +475,25 @@ public abstract class MediaRoute2ProviderService extends Service { */ public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) { Objects.requireNonNull(routes, "routes must not be null"); - mProviderInfo = new MediaRoute2ProviderInfo.Builder() - .addRoutes(routes) - .build(); + List<MediaRoute2Info> sanitizedRoutes = new ArrayList<>(routes.size()); + + for (MediaRoute2Info route : routes) { + if (route.isSystemRouteType()) { + Log.w( + TAG, + "Attempting to add a system route type from a non-system route " + + "provider. Overriding type to TYPE_UNKNOWN. Route: " + + route); + sanitizedRoutes.add( + new MediaRoute2Info.Builder(route) + .setType(MediaRoute2Info.TYPE_UNKNOWN) + .build()); + } else { + sanitizedRoutes.add(route); + } + } + + mProviderInfo = new MediaRoute2ProviderInfo.Builder().addRoutes(sanitizedRoutes).build(); schedulePublishState(); } diff --git a/native/android/Android.bp b/native/android/Android.bp index 8945bd1444f0..c4c41028f969 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -41,6 +41,7 @@ cc_defaults { "-Wextra", "-Wunused", "-Wunreachable-code", + "-Wthread-safety", ], } diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 44fa677b59f7..7e5bef1125f2 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -23,6 +23,7 @@ #include <aidl/android/os/IHintManager.h> #include <aidl/android/os/IHintSession.h> #include <android-base/stringprintf.h> +#include <android-base/thread_annotations.h> #include <android/binder_manager.h> #include <android/binder_status.h> #include <android/performance_hint.h> @@ -111,26 +112,26 @@ private: // HAL preferred update rate const int64_t mPreferredRateNanos; // Target duration for choosing update rate - int64_t mTargetDurationNanos; + int64_t mTargetDurationNanos GUARDED_BY(sHintMutex); // First target hit timestamp - int64_t mFirstTargetMetTimestamp; + int64_t mFirstTargetMetTimestamp GUARDED_BY(sHintMutex); // Last target hit timestamp - int64_t mLastTargetMetTimestamp; + int64_t mLastTargetMetTimestamp GUARDED_BY(sHintMutex); // Last hint reported from sendHint indexed by hint value - std::vector<int64_t> mLastHintSentTimestamp; + std::vector<int64_t> mLastHintSentTimestamp GUARDED_BY(sHintMutex); // Cached samples - std::vector<hal::WorkDuration> mActualWorkDurations; - std::string mSessionName; - static int64_t sIDCounter; + std::vector<hal::WorkDuration> mActualWorkDurations GUARDED_BY(sHintMutex); + std::string mSessionName GUARDED_BY(sHintMutex); + static int64_t sIDCounter GUARDED_BY(sHintMutex); // The most recent set of thread IDs - std::vector<int32_t> mLastThreadIDs; - std::optional<hal::SessionConfig> mSessionConfig; + std::vector<int32_t> mLastThreadIDs GUARDED_BY(sHintMutex); + std::optional<hal::SessionConfig> mSessionConfig GUARDED_BY(sHintMutex); // Tracing helpers - void traceThreads(std::vector<int32_t>& tids); - void tracePowerEfficient(bool powerEfficient); - void traceActualDuration(int64_t actualDuration); - void traceBatchSize(size_t batchSize); - void traceTargetDuration(int64_t targetDuration); + void traceThreads(std::vector<int32_t>& tids) REQUIRES(sHintMutex); + void tracePowerEfficient(bool powerEfficient) REQUIRES(sHintMutex); + void traceActualDuration(int64_t actualDuration) REQUIRES(sHintMutex); + void traceBatchSize(size_t batchSize) REQUIRES(sHintMutex); + void traceTargetDuration(int64_t targetDuration) REQUIRES(sHintMutex); }; static std::shared_ptr<IHintManager>* gIHintManagerForTesting = nullptr; diff --git a/native/android/thermal.cpp b/native/android/thermal.cpp index b43f2f16a7cb..f7a3537d3f4a 100644 --- a/native/android/thermal.cpp +++ b/native/android/thermal.cpp @@ -99,21 +99,21 @@ AThermalManager::AThermalManager(sp<IThermalService> service) : mThermalSvc(std::move(service)), mServiceListener(nullptr) {} AThermalManager::~AThermalManager() { - std::unique_lock<std::mutex> listenerLock(mListenerMutex); - - mListeners.clear(); - if (mServiceListener != nullptr) { - bool success = false; - mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success); - mServiceListener = nullptr; + { + std::scoped_lock<std::mutex> listenerLock(mListenerMutex); + mListeners.clear(); + if (mServiceListener != nullptr) { + bool success = false; + mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success); + mServiceListener = nullptr; + } } - listenerLock.unlock(); - std::unique_lock<std::mutex> lock(mThresholdsMutex); + std::scoped_lock<std::mutex> lock(mThresholdsMutex); delete[] mThresholds; } status_t AThermalManager::notifyStateChange(int32_t status) { - std::unique_lock<std::mutex> lock(mListenerMutex); + std::scoped_lock<std::mutex> lock(mListenerMutex); AThermalStatus thermalStatus = static_cast<AThermalStatus>(status); for (auto listener : mListeners) { @@ -123,7 +123,7 @@ status_t AThermalManager::notifyStateChange(int32_t status) { } status_t AThermalManager::addListener(AThermal_StatusCallback callback, void *data) { - std::unique_lock<std::mutex> lock(mListenerMutex); + std::scoped_lock<std::mutex> lock(mListenerMutex); if (callback == nullptr) { // Callback can not be nullptr @@ -157,7 +157,7 @@ status_t AThermalManager::addListener(AThermal_StatusCallback callback, void *da } status_t AThermalManager::removeListener(AThermal_StatusCallback callback, void *data) { - std::unique_lock<std::mutex> lock(mListenerMutex); + std::scoped_lock<std::mutex> lock(mListenerMutex); auto it = std::remove_if(mListeners.begin(), mListeners.end(), @@ -216,7 +216,7 @@ status_t AThermalManager::getThermalHeadroom(int32_t forecastSeconds, float *res status_t AThermalManager::getThermalHeadroomThresholds(const AThermalHeadroomThreshold **result, size_t *size) { - std::unique_lock<std::mutex> lock(mThresholdsMutex); + std::scoped_lock<std::mutex> lock(mThresholdsMutex); if (mThresholds == nullptr) { auto thresholds = std::make_unique<std::vector<float>>(); binder::Status ret = mThermalSvc->getThermalHeadroomThresholds(thresholds.get()); diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml index d0f797e5c6b8..dd7b03de47cd 100644 --- a/nfc/lint-baseline.xml +++ b/nfc/lint-baseline.xml @@ -212,35 +212,57 @@ <issue id="FlaggedApi" - message="Method `PollingFrame()` is a flagged API and should be inside an `if (Flags.nfcReadPollingLoop())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) to transfer requirement to caller`)" - errorLine1=" pollingFrames.add(new PollingFrame(frame));" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + message="Method `NfcOemExtension()` is a flagged API and should be inside an `if (Flags.nfcOemExtension())` check (or annotate the surrounding method `NfcAdapter` with `@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) to transfer requirement to caller`)" + errorLine1=" mNfcOemExtension = new NfcOemExtension(mContext, this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> <location - file="frameworks/base/nfc/java/android/nfc/cardemulation/HostApduService.java" - line="335" - column="43"/> + file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java" + line="909" + column="28"/> </issue> <issue id="FlaggedApi" - message="Method `processPollingFrames()` is a flagged API and should be inside an `if (Flags.nfcReadPollingLoop())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) to transfer requirement to caller`)" - errorLine1=" processPollingFrames(pollingFrames);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + message="Field `FLAG_SET_DEFAULT_TECH` is a flagged API and should be inside an `if (Flags.nfcSetDefaultDiscTech())` check (or annotate the surrounding method `setDiscoveryTechnology` with `@FlaggedApi(Flags.FLAG_NFC_SET_DEFAULT_DISC_TECH) to transfer requirement to caller`)" + errorLine1=" && ((pollTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> <location - file="frameworks/base/nfc/java/android/nfc/cardemulation/HostApduService.java" - line="337" - column="21"/> + file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java" + line="1917" + column="39"/> </issue> <issue id="FlaggedApi" - message="Method `NfcOemExtension()` is a flagged API and should be inside an `if (Flags.nfcOemExtension())` check (or annotate the surrounding method `NfcAdapter` with `@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) to transfer requirement to caller`)" - errorLine1=" mNfcOemExtension = new NfcOemExtension(mContext, this);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + message="Field `FLAG_SET_DEFAULT_TECH` is a flagged API and should be inside an `if (Flags.nfcSetDefaultDiscTech())` check (or annotate the surrounding method `setDiscoveryTechnology` with `@FlaggedApi(Flags.FLAG_NFC_SET_DEFAULT_DISC_TECH) to transfer requirement to caller`)" + errorLine1=" && ((pollTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> <location file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java" - line="895" - column="28"/> + line="1917" + column="65"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FLAG_SET_DEFAULT_TECH` is a flagged API and should be inside an `if (Flags.nfcSetDefaultDiscTech())` check (or annotate the surrounding method `setDiscoveryTechnology` with `@FlaggedApi(Flags.FLAG_NFC_SET_DEFAULT_DISC_TECH) to transfer requirement to caller`)" + errorLine1=" || (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java" + line="1918" + column="40"/> + </issue> + + <issue + id="FlaggedApi" + message="Field `FLAG_SET_DEFAULT_TECH` is a flagged API and should be inside an `if (Flags.nfcSetDefaultDiscTech())` check (or annotate the surrounding method `setDiscoveryTechnology` with `@FlaggedApi(Flags.FLAG_NFC_SET_DEFAULT_DISC_TECH) to transfer requirement to caller`)" + errorLine1=" || (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java" + line="1918" + column="66"/> </issue> <issue diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java index f98908cf2d56..66ab81bf02b1 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java @@ -16,10 +16,7 @@ package com.android.companiondevicemanager; -import static android.companion.CompanionDeviceManager.REASON_CANCELED; -import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT; -import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR; -import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED; +import static android.companion.CompanionDeviceManager.RESULT_CANCELED; import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT; import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR; import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED; @@ -27,6 +24,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState; import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT; +import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.LOCK; +import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.sDiscoveryStarted; import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICONS; import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_NAMES; import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_PERMISSIONS; @@ -232,8 +231,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false); if (forCancelDialog) { Slog.i(TAG, "Cancelling the user confirmation"); - cancel(/* discoveryTimeOut */ false, /* userRejected */ false, - /* internalError */ false); + cancel(RESULT_CANCELED); return; } @@ -241,8 +239,14 @@ public class CompanionAssociationActivity extends FragmentActivity implements // yet). We can only "process" one request at a time. final IAssociationRequestCallback appCallback = IAssociationRequestCallback.Stub .asInterface(intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK)); + + if (appCallback == null) { + return; + } + Slog.e(TAG, "More than one AssociationRequests are processing."); + try { - requireNonNull(appCallback).onFailure("Busy."); + appCallback.onFailure(RESULT_INTERNAL_ERROR); } catch (RemoteException ignore) { } } @@ -253,8 +257,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements // TODO: handle config changes without cancelling. if (!isDone()) { - cancel(/* discoveryTimeOut */ false, - /* userRejected */ false, /* internalError */ false); // will finish() + cancel(RESULT_CANCELED); // will finish() } } @@ -326,8 +329,11 @@ public class CompanionAssociationActivity extends FragmentActivity implements private void onDiscoveryStateChanged(DiscoveryState newState) { if (newState == FINISHED_TIMEOUT && CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) { - cancel(/* discoveryTimeOut */ true, - /* userRejected */ false, /* internalError */ false); + synchronized (LOCK) { + if (sDiscoveryStarted) { + cancel(RESULT_DISCOVERY_TIMEOUT); + } + } } } @@ -365,7 +371,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data); } - private void cancel(boolean discoveryTimeout, boolean userRejected, boolean internalError) { + private void cancel(int failureCode) { if (isDone()) { Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled")); return; @@ -373,35 +379,19 @@ public class CompanionAssociationActivity extends FragmentActivity implements mCancelled = true; // Stop discovery service if it was used. - if (!mRequest.isSelfManaged() || discoveryTimeout) { + if (!mRequest.isSelfManaged()) { CompanionDeviceDiscoveryService.stop(this); } - final String cancelReason; - final int resultCode; - if (userRejected) { - cancelReason = REASON_USER_REJECTED; - resultCode = RESULT_USER_REJECTED; - } else if (discoveryTimeout) { - cancelReason = REASON_DISCOVERY_TIMEOUT; - resultCode = RESULT_DISCOVERY_TIMEOUT; - } else if (internalError) { - cancelReason = REASON_INTERNAL_ERROR; - resultCode = RESULT_INTERNAL_ERROR; - } else { - cancelReason = REASON_CANCELED; - resultCode = CompanionDeviceManager.RESULT_CANCELED; - } - // First send callback to the app directly... try { - Slog.i(TAG, "Sending onFailure to app due to reason=" + cancelReason); - mAppCallback.onFailure(cancelReason); + Slog.i(TAG, "Sending onFailure to app due to failureCode=" + failureCode); + mAppCallback.onFailure(failureCode); } catch (RemoteException ignore) { } // ... then set result and finish ("sending" onActivityResult()). - setResultAndFinish(null, resultCode); + setResultAndFinish(null, failureCode); } private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) { @@ -446,8 +436,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements } } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found."); - cancel(/* discoveryTimeout */ false, - /* userRejected */ false, /* internalError */ true); + cancel(RESULT_INTERNAL_ERROR); return; } @@ -568,6 +557,8 @@ public class CompanionAssociationActivity extends FragmentActivity implements updateSingleDeviceUi(); + if (mRequest.isSkipPrompt()) return; + mSummary.setVisibility(View.VISIBLE); mButtonAllow.setVisibility(View.VISIBLE); mButtonNotAllow.setVisibility(View.VISIBLE); @@ -629,7 +620,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements // Disable the button, to prevent more clicks. v.setEnabled(false); - cancel(/* discoveryTimeout */ false, /* userRejected */ true, /* internalError */ false); + cancel(RESULT_USER_REJECTED); } private void onShowHelperDialog(View view) { @@ -755,7 +746,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements @Override public void onShowHelperDialogFailed() { - cancel(/* discoveryTimeout */ false, /* userRejected */ false, /* internalError */ true); + cancel(RESULT_INTERNAL_ERROR); } @Override diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index e809433a1261..6c1bc4ededce 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -90,9 +90,6 @@ public class CompanionDeviceDiscoveryService extends Service { new MutableLiveData<>(Collections.emptyList()); private static MutableLiveData<DiscoveryState> sStateLiveData = new MutableLiveData<>(DiscoveryState.NOT_STARTED); - private static final Object LOCK = new Object(); - @GuardedBy("LOCK") - private static boolean sDiscoveryStarted = false; private BluetoothManager mBtManager; private BluetoothAdapter mBtAdapter; @@ -109,6 +106,10 @@ public class CompanionDeviceDiscoveryService extends Service { private boolean mStopAfterFirstMatch; + static final Object LOCK = new Object(); + @GuardedBy("LOCK") + static boolean sDiscoveryStarted = false; + /** * A state enum for devices' discovery. */ @@ -127,6 +128,7 @@ public class CompanionDeviceDiscoveryService extends Service { return false; } } + sScanResultsLiveData.setValue(Collections.emptyList()); requireNonNull(associationRequest); final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class); intent.setAction(ACTION_START_DISCOVERY); @@ -192,7 +194,6 @@ public class CompanionDeviceDiscoveryService extends Service { sDiscoveryStarted = true; } mStopAfterFirstMatch = request.isSingleDevice(); - sScanResultsLiveData.setValue(Collections.emptyList()); sStateLiveData.setValue(DiscoveryState.IN_PROGRESS); final List<DeviceFilter<?>> allFilters = request.getDeviceFilters(); diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt index 5728c8cb9263..35addb341c72 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -24,7 +24,6 @@ import androidx.activity.compose.setContent import androidx.activity.viewModels import com.android.credentialmanager.ui.theme.WearCredentialSelectorTheme import com.android.credentialmanager.ui.WearApp -import com.google.android.horologist.annotations.ExperimentalHorologistApi import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint(ComponentActivity::class) @@ -32,7 +31,6 @@ class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() { private val viewModel: CredentialSelectorViewModel by viewModels() - @OptIn(ExperimentalHorologistApi::class) override fun onCreate(savedInstanceState: Bundle?) { Log.d(TAG, "onCreate, intent: $intent") super.onCreate(savedInstanceState) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt index c641d7f9f48f..25bc38133e38 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt @@ -15,6 +15,7 @@ */ package com.android.credentialmanager.ui.components +import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme import androidx.compose.foundation.layout.Row import androidx.compose.material3.Icon import android.graphics.drawable.Drawable @@ -22,7 +23,11 @@ import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Lock +import androidx.compose.material.icons.outlined.LockOpen import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -46,7 +51,7 @@ fun CredentialsScreenChip( onClick: () -> Unit, secondaryLabel: String? = null, icon: Drawable? = null, - isAuthenticationEntryLocked: Boolean = false, + isAuthenticationEntryLocked: Boolean? = null, textAlign: TextAlign = TextAlign.Center, modifier: Modifier = Modifier, colors: ChipColors = ChipDefaults.secondaryChipColors() @@ -77,7 +82,7 @@ fun CredentialsScreenChip( text: @Composable () -> Unit, secondaryLabel: String? = null, icon: Drawable? = null, - isAuthenticationEntryLocked: Boolean = false, + isAuthenticationEntryLocked: Boolean? = null, modifier: Modifier = Modifier, colors: ChipColors = ChipDefaults.primaryChipColors(), ) { @@ -94,16 +99,23 @@ fun CredentialsScreenChip( text = secondaryLabel, ) - if (isAuthenticationEntryLocked) - // TODO(b/324465527) change this to lock icon and correct size once figma mocks are - // updated - Icon( - bitmap = checkNotNull(icon?.toBitmap()?.asImageBitmap()), - // Decorative purpose only. - contentDescription = null, - modifier = Modifier.size(10.dp), - tint = Color.Unspecified - ) + if (isAuthenticationEntryLocked != null) { + if (isAuthenticationEntryLocked) { + Icon( + Icons.Outlined.Lock, + contentDescription = null, + modifier = Modifier.size(12.dp).align(Alignment.CenterVertically), + tint = WearMaterialTheme.colors.onSurfaceVariant + ) + } else { + Icon( + Icons.Outlined.LockOpen, + contentDescription = null, + modifier = Modifier.size(12.dp).align(Alignment.CenterVertically), + tint = WearMaterialTheme.colors.onSurfaceVariant + ) + } + } } } } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt index 22f6bf0f37ee..282fea0d7218 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt @@ -22,7 +22,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -57,7 +56,6 @@ fun WearUsernameText( text: String, textAlign: TextAlign = TextAlign.Center, modifier: Modifier = Modifier, - onTextLayout: (TextLayoutResult) -> Unit = {}, ) { Text( modifier = modifier.padding(start = 8.dp, end = 8.dp).wrapContentSize(), @@ -67,7 +65,6 @@ fun WearUsernameText( overflow = TextOverflow.Ellipsis, textAlign = textAlign, maxLines = 2, - onTextLayout = onTextLayout, ) } @@ -79,7 +76,6 @@ fun WearButtonText( maxLines: Int = 1, modifier: Modifier = Modifier, color: Color = WearMaterialTheme.colors.onSurface, - onTextLayout: (TextLayoutResult) -> Unit = {}, ) { Text( modifier = modifier.wrapContentSize(), @@ -89,7 +85,6 @@ fun WearButtonText( overflow = TextOverflow.Ellipsis, textAlign = textAlign, maxLines = maxLines, - onTextLayout = onTextLayout, ) } @@ -97,7 +92,6 @@ fun WearButtonText( fun WearSecondaryLabel( text: String, modifier: Modifier = Modifier, - onTextLayout: (TextLayoutResult) -> Unit = {}, ) { Text( modifier = modifier.wrapContentSize(), @@ -107,6 +101,5 @@ fun WearSecondaryLabel( overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Start, maxLines = 1, - onTextLayout = onTextLayout, ) } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt index 473094cc1308..265627520cf7 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt @@ -43,7 +43,8 @@ fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get { var icon: Drawable? = null // provide icon if all entries have the same provider - if (sortedEntries.all {it.providerId == sortedEntries[0].providerId}) { + if (sortedEntries.isNotEmpty() && + sortedEntries.all {it.providerId == sortedEntries[0].providerId}) { icon = providerInfos[0].icon } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt index fb81e736171b..36e97929e883 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt @@ -75,7 +75,10 @@ fun MultiCredentialsFlattenScreen( CredentialsScreenChip( label = credential.userName, onClick = { selectEntry(credential, false) }, - secondaryLabel = credential.credentialTypeDisplayName, + secondaryLabel = + credential.credentialTypeDisplayName.ifEmpty { + credential.providerDisplayName + }, icon = credential.icon, textAlign = TextAlign.Start ) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt index 7addc74aecd0..ce2bad0f71ad 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt @@ -61,13 +61,12 @@ fun MultiCredentialsFoldScreen( val credentials = credentialSelectorUiState.sortedEntries item { var title = stringResource(R.string.choose_sign_in_title) - - if (credentials.isEmpty()) { - title = stringResource(R.string.choose_sign_in_title) - } else if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) { - title = stringResource(R.string.choose_passkey_title) - } else if (credentials.all { it.credentialType == CredentialType.PASSWORD }) { - title = stringResource(R.string.choose_password_title) + if (credentials.isNotEmpty()) { + if (credentials.all { it.credentialType == CredentialType.PASSKEY }) { + title = stringResource(R.string.choose_passkey_title) + } else if (credentials.all { it.credentialType == CredentialType.PASSWORD }) { + title = stringResource(R.string.choose_password_title) + } } SignInHeader( @@ -77,16 +76,19 @@ fun MultiCredentialsFoldScreen( } credentials.forEach { credential: CredentialEntryInfo -> - item { - CredentialsScreenChip( - label = credential.userName, - onClick = { selectEntry(credential, false) }, - secondaryLabel = credential.credentialTypeDisplayName, - icon = credential.icon, - ) - CredentialsScreenChipSpacer() + item { + CredentialsScreenChip( + label = credential.userName, + onClick = { selectEntry(credential, false) }, + secondaryLabel = + credential.credentialTypeDisplayName.ifEmpty { + credential.providerDisplayName + }, + icon = credential.icon, + ) + CredentialsScreenChipSpacer() + } } - } credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo -> item { @@ -96,7 +98,6 @@ fun MultiCredentialsFoldScreen( CredentialsScreenChipSpacer() } } - item { Spacer(modifier = Modifier.size(8.dp)) } diff --git a/packages/CtsShim/apk/riscv64/CtsShim.apk b/packages/CtsShim/apk/riscv64/CtsShim.apk Binary files differindex af306a504d30..5ab190d19514 100644 --- a/packages/CtsShim/apk/riscv64/CtsShim.apk +++ b/packages/CtsShim/apk/riscv64/CtsShim.apk diff --git a/packages/CtsShim/apk/riscv64/CtsShimPriv.apk b/packages/CtsShim/apk/riscv64/CtsShimPriv.apk Binary files differindex 9a9997dc4155..441f86f04625 100644 --- a/packages/CtsShim/apk/riscv64/CtsShimPriv.apk +++ b/packages/CtsShim/apk/riscv64/CtsShimPriv.apk diff --git a/packages/InputDevices/res/values-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml index 74fd7c54dedf..3fa4bce3dfc3 100644 --- a/packages/InputDevices/res/values-en-rCA/strings.xml +++ b/packages/InputDevices/res/values-en-rCA/strings.xml @@ -54,8 +54,6 @@ <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbian (Latin)"</string> <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Montenegrin (Latin)"</string> - <!-- no translation found for keyboard_layout_serbian_cyrillic (7013541044323542196) --> - <skip /> - <!-- no translation found for keyboard_layout_montenegrin_cyrillic (2391253952894077421) --> - <skip /> + <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"Serbian (Cyrillic)"</string> + <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"Montenegrin (Cyrillic)"</string> </resources> diff --git a/packages/InputDevices/res/values-en-rXC/strings.xml b/packages/InputDevices/res/values-en-rXC/strings.xml index 777d93913121..2ae35abfab02 100644 --- a/packages/InputDevices/res/values-en-rXC/strings.xml +++ b/packages/InputDevices/res/values-en-rXC/strings.xml @@ -54,8 +54,6 @@ <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbian (Latin)"</string> <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Montenegrin (Latin)"</string> - <!-- no translation found for keyboard_layout_serbian_cyrillic (7013541044323542196) --> - <skip /> - <!-- no translation found for keyboard_layout_montenegrin_cyrillic (2391253952894077421) --> - <skip /> + <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"Serbian (Cyrillic)"</string> + <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"Montenegrin (Cyrillic)"</string> </resources> diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 9a344c3d7f14..2ae3b569bc70 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -41,23 +41,25 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.NonRestartableComposable -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.AlignmentLine import androidx.compose.ui.layout.LastBaseline import androidx.compose.ui.layout.Layout @@ -66,6 +68,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow @@ -80,11 +83,10 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.roundToInt -private val windowInsets: WindowInsets +private val safeDrawingWindowInsets: WindowInsets @Composable @NonRestartableComposable - get() = WindowInsets.safeDrawing - .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) + get() = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) @Composable internal fun CustomizedTopAppBar( @@ -97,14 +99,12 @@ internal fun CustomizedTopAppBar( titleTextStyle = MaterialTheme.typography.titleMedium, navigationIcon = navigationIcon, actions = actions, - windowInsets = windowInsets, + windowInsets = safeDrawingWindowInsets, colors = topAppBarColors(), ) } -/** - * The customized LargeTopAppBar for Settings. - */ +/** The customized LargeTopAppBar for Settings. */ @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun CustomizedLargeTopAppBar( @@ -124,7 +124,7 @@ internal fun CustomizedLargeTopAppBar( navigationIcon = navigationIcon, actions = actions, colors = topAppBarColors(), - windowInsets = windowInsets, + windowInsets = safeDrawingWindowInsets, pinnedHeight = ContainerHeight, scrollBehavior = scrollBehavior, ) @@ -134,38 +134,41 @@ internal fun CustomizedLargeTopAppBar( private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) { Text( text = title, - modifier = Modifier.padding( - start = SettingsDimension.itemPaddingAround, - end = SettingsDimension.itemPaddingEnd, - ) - .semantics { heading() }, + modifier = + Modifier.padding( + start = SettingsDimension.itemPaddingAround, + end = SettingsDimension.itemPaddingEnd, + ) + .semantics { heading() }, overflow = TextOverflow.Ellipsis, maxLines = maxLines, ) } @Composable -private fun topAppBarColors() = TopAppBarColors( - containerColor = MaterialTheme.colorScheme.settingsBackground, - scrolledContainerColor = MaterialTheme.colorScheme.surfaceVariant, - navigationIconContentColor = MaterialTheme.colorScheme.onSurface, - titleContentColor = MaterialTheme.colorScheme.onSurface, - actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, -) +private fun topAppBarColors() = + TopAppBarColors( + containerColor = MaterialTheme.colorScheme.settingsBackground, + scrolledContainerColor = MaterialTheme.colorScheme.surfaceVariant, + navigationIconContentColor = MaterialTheme.colorScheme.onSurface, + titleContentColor = MaterialTheme.colorScheme.onSurface, + actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, + ) /** * Represents the colors used by a top app bar in different states. + * * This implementation animates the container color according to the top app bar scroll state. It * does not animate the leading, headline, or trailing colors. * - * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a - * factory method using the default material3 spec * @param containerColor the color used for the background of this BottomAppBar. Use - * [Color.Transparent] to have no color. + * [Color.Transparent] to have no color. * @param scrolledContainerColor the container color when content is scrolled behind it * @param navigationIconContentColor the content color used for the navigation icon * @param titleContentColor the content color used for the title * @param actionIconContentColor the content color used for actions + * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a factory method + * using the default material3 spec */ @Stable private class TopAppBarColors( @@ -180,11 +183,11 @@ private class TopAppBarColors( * Represents the container color used for the top app bar. * * A [colorTransitionFraction] provides a percentage value that can be used to generate a color. - * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from - * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction]. + * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from the + * [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction]. * * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition - * percentage + * percentage */ @Stable fun containerColor(colorTransitionFraction: Float): Color { @@ -233,29 +236,35 @@ private fun SingleRowTopAppBar( colors: TopAppBarColors, ) { // Wrap the given actions in a Row. - val actionsRow = @Composable { - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - content = actions - ) - } + val actionsRow = + @Composable { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + content = actions + ) + } // Compose a Surface with a TopAppBarLayout content. - Surface(color = colors.scrolledContainerColor) { + Box( + modifier = + Modifier.drawBehind { drawRect(color = colors.scrolledContainerColor) } + .semantics { isTraversalGroup = true } + .pointerInput(Unit) {} + ) { val height = LocalDensity.current.run { ContainerHeight.toPx() } TopAppBarLayout( - modifier = Modifier - .windowInsetsPadding(windowInsets) - // clip after padding so we don't show the title over the inset area - .clipToBounds(), + modifier = + Modifier.windowInsetsPadding(windowInsets) + // clip after padding so we don't show the title over the inset area + .clipToBounds(), heightPx = height, navigationIconContentColor = colors.navigationIconContentColor, titleContentColor = colors.titleContentColor, actionIconContentColor = colors.actionIconContentColor, title = title, titleTextStyle = titleTextStyle, - titleAlpha = 1f, + titleAlpha = { 1f }, titleVerticalArrangement = Arrangement.Center, titleBottomPadding = 0, hideTitleSemantics = false, @@ -271,7 +280,7 @@ private fun SingleRowTopAppBar( * composables. * * @throws [IllegalArgumentException] if the given [MaxHeightWithoutTitle] is equal or smaller than - * the [pinnedHeight] + * the [pinnedHeight] */ @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -308,62 +317,67 @@ private fun TwoRowsTopAppBar( // Sets the app bar's height offset limit to hide just the bottom title area and keep top title // visible when collapsed. - SideEffect { - if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) { - scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue - } - } + scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or // collapse. // This will potentially animate or interpolate a transition between the container color and the // container's scrolled color according to the app bar's scroll state. - val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f - val appBarContainerColor = colors.containerColor(colorTransitionFraction) + val colorTransitionFraction = { scrollBehavior?.state?.collapsedFraction ?: 0f } + val appBarContainerColor = { colors.containerColor(colorTransitionFraction()) } // Wrap the given actions in a Row. - val actionsRow = @Composable { - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - content = actions - ) - } - val topTitleAlpha = TopTitleAlphaEasing.transform(colorTransitionFraction) - val bottomTitleAlpha = 1f - colorTransitionFraction + val actionsRow = + @Composable { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + content = actions + ) + } + val topTitleAlpha = { TopTitleAlphaEasing.transform(colorTransitionFraction()) } + val bottomTitleAlpha = { 1f - colorTransitionFraction() } // Hide the top row title semantics when its alpha value goes below 0.5 threshold. // Hide the bottom row title semantics when the top title semantics are active. - val hideTopRowSemantics = colorTransitionFraction < 0.5f + val hideTopRowSemantics by + remember(colorTransitionFraction) { derivedStateOf { colorTransitionFraction() < 0.5f } } val hideBottomRowSemantics = !hideTopRowSemantics // Set up support for resizing the top app bar when vertically dragging the bar itself. - val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) { - Modifier.draggable( - orientation = Orientation.Vertical, - state = rememberDraggableState { delta -> - scrollBehavior.state.heightOffset += delta - }, - onDragStopped = { velocity -> - settleAppBar( - scrollBehavior.state, - velocity, - scrollBehavior.flingAnimationSpec, - scrollBehavior.snapAnimationSpec - ) - } - ) - } else { - Modifier - } + val appBarDragModifier = + if (scrollBehavior != null && !scrollBehavior.isPinned) { + Modifier.draggable( + orientation = Orientation.Vertical, + state = + rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta }, + onDragStopped = { velocity -> + settleAppBar( + scrollBehavior.state, + velocity, + scrollBehavior.flingAnimationSpec, + scrollBehavior.snapAnimationSpec + ) + } + ) + } else { + Modifier + } - Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) { + Box( + modifier = + modifier + .then(appBarDragModifier) + .drawBehind { drawRect(color = appBarContainerColor()) } + .semantics { isTraversalGroup = true } + .pointerInput(Unit) {} + ) { Column { TopAppBarLayout( - modifier = Modifier - .windowInsetsPadding(windowInsets) - // clip after padding so we don't show the title over the inset area - .clipToBounds(), + modifier = + Modifier.windowInsetsPadding(windowInsets) + // clip after padding so we don't show the title over the inset area + .clipToBounds(), heightPx = pinnedHeightPx, navigationIconContentColor = colors.navigationIconContentColor, titleContentColor = colors.titleContentColor, @@ -378,27 +392,37 @@ private fun TwoRowsTopAppBar( actions = actionsRow, ) TopAppBarLayout( - modifier = Modifier - // only apply the horizontal sides of the window insets padding, since the top - // padding will always be applied by the layout above - .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal)) - .clipToBounds(), - heightPx = maxHeightPx.floatValue - pinnedHeightPx + - (scrollBehavior?.state?.heightOffset ?: 0f), + modifier = + Modifier + // only apply the horizontal sides of the window insets padding, since the + // top + // padding will always be applied by the layout above + .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal)) + .clipToBounds(), + heightPx = + maxHeightPx.floatValue - pinnedHeightPx + + (scrollBehavior?.state?.heightOffset ?: 0f), navigationIconContentColor = colors.navigationIconContentColor, titleContentColor = colors.titleContentColor, actionIconContentColor = colors.actionIconContentColor, title = { - Box(modifier = Modifier.onGloballyPositioned { coordinates -> - val measuredMaxHeightPx = density.run { - MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat() - } - // Allow larger max height for multi-line title, but do not reduce - // max height to prevent flaky. - if (measuredMaxHeightPx > defaultMaxHeightPx) { - maxHeightPx.floatValue = measuredMaxHeightPx - } - }) { title() } + Box( + modifier = + Modifier.onGloballyPositioned { coordinates -> + val measuredMaxHeightPx = + density.run { + MaxHeightWithoutTitle.toPx() + + coordinates.size.height.toFloat() + } + // Allow larger max height for multi-line title, but do not reduce + // max height to prevent flaky. + if (measuredMaxHeightPx > defaultMaxHeightPx) { + maxHeightPx.floatValue = measuredMaxHeightPx + } + } + ) { + title() + } }, titleTextStyle = titleTextStyle, titleAlpha = bottomTitleAlpha, @@ -420,21 +444,21 @@ private fun TwoRowsTopAppBar( * @param modifier a [Modifier] * @param heightPx the total height this layout is capped to * @param navigationIconContentColor the content color that will be applied via a - * [LocalContentColor] when composing the navigation icon + * [LocalContentColor] when composing the navigation icon * @param titleContentColor the color that will be applied via a [LocalContentColor] when composing - * the title + * the title * @param actionIconContentColor the content color that will be applied via a [LocalContentColor] - * when composing the action icons + * when composing the action icons * @param title the top app bar title (header) * @param titleTextStyle the title's text style * @param modifier a [Modifier] * @param titleAlpha the title's alpha * @param titleVerticalArrangement the title's vertical arrangement * @param titleBottomPadding the title's bottom padding - * @param hideTitleSemantics hides the title node from the semantic tree. Apply this - * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics - * from accessibility services. This is needed to avoid having multiple titles visible to - * accessibility services at the same time, when animating between collapsed / expanded states. + * @param hideTitleSemantics hides the title node from the semantic tree. Apply this boolean when + * this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics from accessibility + * services. This is needed to avoid having multiple titles visible to accessibility services at + * the same time, when animating between collapsed / expanded states. * @param navigationIcon a navigation icon [Composable] * @param actions actions [Composable] * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled. @@ -448,7 +472,7 @@ private fun TopAppBarLayout( actionIconContentColor: Color, title: @Composable () -> Unit, titleTextStyle: TextStyle, - titleAlpha: Float, + titleAlpha: () -> Float, titleVerticalArrangement: Arrangement.Vertical, titleBottomPadding: Int, hideTitleSemantics: Boolean, @@ -458,41 +482,33 @@ private fun TopAppBarLayout( ) { Layout( { - Box( - Modifier - .layoutId("navigationIcon") - .padding(start = TopAppBarHorizontalPadding) - ) { + Box(Modifier.layoutId("navigationIcon").padding(start = TopAppBarHorizontalPadding)) { CompositionLocalProvider( LocalContentColor provides navigationIconContentColor, content = navigationIcon ) } Box( - Modifier - .layoutId("title") + Modifier.layoutId("title") .padding(horizontal = TopAppBarHorizontalPadding) - .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics { } else Modifier) - .graphicsLayer(alpha = titleAlpha) + .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics {} else Modifier) + .graphicsLayer { alpha = titleAlpha() } ) { ProvideTextStyle(value = titleTextStyle) { CompositionLocalProvider( LocalContentColor provides titleContentColor, - LocalDensity provides with(LocalDensity.current) { - Density( - density = density, - fontScale = if (titleScaleDisabled) 1f else fontScale, - ) - }, + LocalDensity provides + with(LocalDensity.current) { + Density( + density = density, + fontScale = if (titleScaleDisabled) 1f else fontScale, + ) + }, content = title ) } } - Box( - Modifier - .layoutId("actionIcons") - .padding(end = TopAppBarHorizontalPadding) - ) { + Box(Modifier.layoutId("actionIcons").padding(end = TopAppBarHorizontalPadding)) { CompositionLocalProvider( LocalContentColor provides actionIconContentColor, content = actions @@ -502,20 +518,24 @@ private fun TopAppBarLayout( modifier = modifier ) { measurables, constraints -> val navigationIconPlaceable = - measurables.first { it.layoutId == "navigationIcon" } + measurables + .first { it.layoutId == "navigationIcon" } .measure(constraints.copy(minWidth = 0)) val actionIconsPlaceable = - measurables.first { it.layoutId == "actionIcons" } + measurables + .first { it.layoutId == "actionIcons" } .measure(constraints.copy(minWidth = 0)) - val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) { - constraints.maxWidth - } else { - (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width) - .coerceAtLeast(0) - } + val maxTitleWidth = + if (constraints.maxWidth == Constraints.Infinity) { + constraints.maxWidth + } else { + (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width) + .coerceAtLeast(0) + } val titlePlaceable = - measurables.first { it.layoutId == "title" } + measurables + .first { it.layoutId == "title" } .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth)) // Locate the title's baseline. @@ -538,19 +558,23 @@ private fun TopAppBarLayout( // Title titlePlaceable.placeRelative( x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width), - y = when (titleVerticalArrangement) { - Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2 - // Apply bottom padding from the title's baseline only when the Arrangement is - // "Bottom". - Arrangement.Bottom -> - if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height - else layoutHeight - titlePlaceable.height - max( - 0, - titleBottomPadding - titlePlaceable.height + titleBaseline - ) - // Arrangement.Top - else -> 0 - } + y = + when (titleVerticalArrangement) { + Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2 + // Apply bottom padding from the title's baseline only when the Arrangement + // is "Bottom". + Arrangement.Bottom -> + if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height + else + layoutHeight - + titlePlaceable.height - + max( + 0, + titleBottomPadding - titlePlaceable.height + titleBaseline + ) + // Arrangement.Top + else -> 0 + } ) // Action icons @@ -562,7 +586,6 @@ private fun TopAppBarLayout( } } - /** * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping * after the fling settles. @@ -587,9 +610,9 @@ private suspend fun settleAppBar( if (flingAnimationSpec != null && abs(velocity) > 1f) { var lastValue = 0f AnimationState( - initialValue = 0f, - initialVelocity = velocity, - ) + initialValue = 0f, + initialVelocity = velocity, + ) .animateDecay(flingAnimationSpec) { val delta = value - lastValue val initialHeightOffset = state.heightOffset @@ -603,9 +626,7 @@ private suspend fun settleAppBar( } // Snap if animation specs were provided. if (snapAnimationSpec != null) { - if (state.heightOffset < 0 && - state.heightOffset > state.heightOffsetLimit - ) { + if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit) { AnimationState(initialValue = state.heightOffset).animateTo( if (state.collapsedFraction < 0.5f) { 0f @@ -613,7 +634,9 @@ private suspend fun settleAppBar( state.heightOffsetLimit }, animationSpec = snapAnimationSpec - ) { state.heightOffset = value } + ) { + state.heightOffset = value + } } } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt index ea69eabbe971..0a4f0d937600 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt @@ -37,15 +37,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.semantics import androidx.compose.ui.test.assertHeightIsEqualTo import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo -import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo import androidx.compose.ui.test.assertWidthIsEqualTo -import androidx.compose.ui.test.getUnclippedBoundsInRoot import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText @@ -53,33 +49,24 @@ import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeLeft import androidx.compose.ui.test.swipeRight import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.height -import androidx.compose.ui.unit.width import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.rootWidth import com.android.settingslib.spa.testutils.setContentForSizeAssertions import com.google.common.truth.Truth.assertThat -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalMaterial3Api::class) -@Ignore("b/346785755") @RunWith(AndroidJUnit4::class) class CustomizedAppBarTest { - @get:Rule - val rule = createComposeRule() + @get:Rule val rule = createComposeRule() @Test fun smallTopAppBar_expandsToScreen() { rule - .setContentForSizeAssertions { - CustomizedTopAppBar(title = { Text("Title") }) - } + .setContentForSizeAssertions { CustomizedTopAppBar(title = { Text("Title") }) } .assertHeightIsEqualTo(ContainerHeight) .assertWidthIsEqualTo(rule.rootWidth()) } @@ -88,51 +75,12 @@ class CustomizedAppBarTest { fun smallTopAppBar_withTitle() { val title = "Title" rule.setContent { - Box(Modifier.testTag(TopAppBarTestTag)) { - CustomizedTopAppBar(title = { Text(title) }) - } + Box(Modifier.testTag(TopAppBarTestTag)) { CustomizedTopAppBar(title = { Text(title) }) } } rule.onNodeWithText(title).assertIsDisplayed() } @Test - fun smallTopAppBar_default_positioning() { - rule.setContent { - Box(Modifier.testTag(TopAppBarTestTag)) { - CustomizedTopAppBar( - navigationIcon = { - FakeIcon(Modifier.testTag(NavigationIconTestTag)) - }, - title = { - Text("Title", Modifier.testTag(TitleTestTag)) - }, - actions = { - FakeIcon(Modifier.testTag(ActionsTestTag)) - } - ) - } - } - assertSmallDefaultPositioning() - } - - @Test - fun smallTopAppBar_noNavigationIcon_positioning() { - rule.setContent { - Box(Modifier.testTag(TopAppBarTestTag)) { - CustomizedTopAppBar( - title = { - Text("Title", Modifier.testTag(TitleTestTag)) - }, - actions = { - FakeIcon(Modifier.testTag(ActionsTestTag)) - } - ) - } - } - assertSmallPositioningWithoutNavigation() - } - - @Test fun smallTopAppBar_titleDefaultStyle() { var textStyle: TextStyle? = null var expectedTextStyle: TextStyle? = null @@ -188,29 +136,6 @@ class CustomizedAppBarTest { assertThat(actionsColor).isEqualTo(expectedActionsColor) } - @Test - fun largeTopAppBar_scrolled_positioning() { - val content = @Composable { scrollBehavior: TopAppBarScrollBehavior? -> - Box(Modifier.testTag(TopAppBarTestTag)) { - CustomizedLargeTopAppBar( - navigationIcon = { - FakeIcon(Modifier.testTag(NavigationIconTestTag)) - }, - title = "Title", - actions = { - FakeIcon(Modifier.testTag(ActionsTestTag)) - }, - scrollBehavior = scrollBehavior, - ) - } - } - assertLargeScrolledHeight( - MaxHeightWithoutTitle + DefaultTitleHeight, - MaxHeightWithoutTitle + DefaultTitleHeight, - content, - ) - } - @OptIn(ExperimentalMaterial3Api::class) @Test fun topAppBar_enterAlways_allowHorizontalScroll() { @@ -221,14 +146,10 @@ class CustomizedAppBarTest { } rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() } - rule.runOnIdle { - assertThat(state.firstVisibleItemIndex).isEqualTo(1) - } + rule.runOnIdle { assertThat(state.firstVisibleItemIndex).isEqualTo(1) } rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() } - rule.runOnIdle { - assertThat(state.firstVisibleItemIndex).isEqualTo(0) - } + rule.runOnIdle { assertThat(state.firstVisibleItemIndex).isEqualTo(0) } } @OptIn(ExperimentalMaterial3Api::class) @@ -241,14 +162,10 @@ class CustomizedAppBarTest { } rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() } - rule.runOnIdle { - assertThat(state.firstVisibleItemIndex).isEqualTo(1) - } + rule.runOnIdle { assertThat(state.firstVisibleItemIndex).isEqualTo(1) } rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() } - rule.runOnIdle { - assertThat(state.firstVisibleItemIndex).isEqualTo(0) - } + rule.runOnIdle { assertThat(state.firstVisibleItemIndex).isEqualTo(0) } } @OptIn(ExperimentalMaterial3Api::class) @@ -257,21 +174,14 @@ class CustomizedAppBarTest { lateinit var state: LazyListState rule.setContent { state = rememberLazyListState() - MultiPageContent( - TopAppBarDefaults.pinnedScrollBehavior(), - state - ) + MultiPageContent(TopAppBarDefaults.pinnedScrollBehavior(), state) } rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() } - rule.runOnIdle { - assertThat(state.firstVisibleItemIndex).isEqualTo(1) - } + rule.runOnIdle { assertThat(state.firstVisibleItemIndex).isEqualTo(1) } rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() } - rule.runOnIdle { - assertThat(state.firstVisibleItemIndex).isEqualTo(0) - } + rule.runOnIdle { assertThat(state.firstVisibleItemIndex).isEqualTo(0) } } @OptIn(ExperimentalMaterial3Api::class) @@ -285,11 +195,7 @@ class CustomizedAppBarTest { ) } ) { contentPadding -> - LazyRow( - Modifier - .fillMaxSize() - .testTag(LazyListTag), state - ) { + LazyRow(Modifier.fillMaxSize().testTag(LazyListTag), state) { items(2) { page -> LazyColumn( modifier = Modifier.fillParentMaxSize(), @@ -308,146 +214,19 @@ class CustomizedAppBarTest { } /** - * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] - * or a larger app bar that is scrolled up and collapsed into a small - * configuration and there is no navigation icon. - */ - private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) { - val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot() - val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot() - - val titleNode = rule.onNodeWithTag(TitleTestTag) - // Title should be vertically centered - titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2) - if (isCenteredTitle) { - // Title should be horizontally centered - titleNode.assertLeftPositionInRootIsEqualTo( - (appBarBounds.width - titleBounds.width) / 2 - ) - } else { - // Title should now be placed 16.dp from the start, as there is no navigation icon - // 4.dp padding for the whole app bar + 12.dp inset - titleNode.assertLeftPositionInRootIsEqualTo(4.dp + 12.dp) - } - - rule.onNodeWithTag(ActionsTestTag) - // Action should still be placed at the end - .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width)) - } - - /** - * Checks the app bar's components positioning when it's a [CustomizedTopAppBar]. - */ - private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) { - val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot() - val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot() - val appBarBottomEdgeY = appBarBounds.top + appBarBounds.height - - rule.onNodeWithTag(NavigationIconTestTag) - // Navigation icon should be 4.dp from the start - .assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding) - // Navigation icon should be centered within the height of the app bar. - .assertTopPositionInRootIsEqualTo( - appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize - ) - - val titleNode = rule.onNodeWithTag(TitleTestTag) - // Title should be vertically centered - titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2) - if (isCenteredTitle) { - // Title should be horizontally centered - titleNode.assertLeftPositionInRootIsEqualTo( - (appBarBounds.width - titleBounds.width) / 2 - ) - } else { - // Title should be 56.dp from the start - // 4.dp padding for the whole app bar + 48.dp icon size + 4.dp title padding. - titleNode.assertLeftPositionInRootIsEqualTo(4.dp + FakeIconSize + 4.dp) - } - - rule.onNodeWithTag(ActionsTestTag) - // Action should be placed at the end - .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width)) - // Action should be 8.dp from the top - .assertTopPositionInRootIsEqualTo( - appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize - ) - } - - /** - * Checks that changing values at a [CustomizedLargeTopAppBar] scroll behavior - * affects the height of the app bar. - * - * This check partially and fully collapses the app bar to test its height. - * - * @param appBarMaxHeight the max height of the app bar [content] - * @param appBarMinHeight the min height of the app bar [content] - * @param content a Composable that adds a CustomizedLargeTopAppBar - */ - @OptIn(ExperimentalMaterial3Api::class) - private fun assertLargeScrolledHeight( - appBarMaxHeight: Dp, - appBarMinHeight: Dp, - content: @Composable (TopAppBarScrollBehavior?) -> Unit - ) { - val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight - val partiallyCollapsedOffsetDp = fullyCollapsedOffsetDp / 3 - var partiallyCollapsedHeightOffsetPx = 0f - var fullyCollapsedHeightOffsetPx = 0f - lateinit var scrollBehavior: TopAppBarScrollBehavior - rule.setContent { - scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - with(LocalDensity.current) { - partiallyCollapsedHeightOffsetPx = partiallyCollapsedOffsetDp.toPx() - fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx() - } - - content(scrollBehavior) - } - - // Simulate a partially collapsed app bar. - rule.runOnIdle { - scrollBehavior.state.heightOffset = -partiallyCollapsedHeightOffsetPx - scrollBehavior.state.contentOffset = -partiallyCollapsedHeightOffsetPx - } - rule.waitForIdle() - rule.onNodeWithTag(TopAppBarTestTag) - .assertHeightIsEqualTo( - appBarMaxHeight - partiallyCollapsedOffsetDp - ) - - // Simulate a fully collapsed app bar. - rule.runOnIdle { - scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx - // Simulate additional content scroll beyond the max offset scroll. - scrollBehavior.state.contentOffset = - -fullyCollapsedHeightOffsetPx - partiallyCollapsedHeightOffsetPx - } - rule.waitForIdle() - // Check that the app bar collapsed to its min height. - rule.onNodeWithTag(TopAppBarTestTag).assertHeightIsEqualTo(appBarMinHeight) - } - - /** * An [IconButton] with an [Icon] inside for testing positions. * * An [IconButton] is defaulted to be 48X48dp, while its child [Icon] is defaulted to 24x24dp. */ - private val FakeIcon = @Composable { modifier: Modifier -> - IconButton( - onClick = { /* doSomething() */ }, - modifier = modifier.semantics(mergeDescendants = true) {} - ) { - Icon(ColorPainter(Color.Red), null) + private val FakeIcon = + @Composable { modifier: Modifier -> + IconButton( + onClick = { /* doSomething() */ }, + modifier = modifier.semantics(mergeDescendants = true) {} + ) { + Icon(ColorPainter(Color.Red), null) + } } - } - - private fun expectedActionPosition(appBarWidth: Dp): Dp = - appBarWidth - AppBarStartAndEndPadding - FakeIconSize - - private val FakeIconSize = 48.dp - private val AppBarStartAndEndPadding = 4.dp - private val AppBarTopAndBottomPadding = (ContainerHeight - FakeIconSize) / 2 private val LazyListTag = "lazyList" private val TopAppBarTestTag = "bar" diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt index 0436fc275b60..53d45317efff 100644 --- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt +++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt @@ -17,6 +17,7 @@ package com.android.settingslib.spa.testutils import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.sizeIn import androidx.compose.material3.Surface import androidx.compose.runtime.Composable @@ -75,7 +76,7 @@ fun ComposeContentTestRule.setContentForSizeAssertions( setContent { SettingsTheme { Surface { - Box { + Box(Modifier.safeDrawingPadding()) { Box( Modifier .sizeIn(maxWidth = parentMaxWidth, maxHeight = parentMaxHeight) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBoolean.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBoolean.kt index 5d67f57136e3..425286953759 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBoolean.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBoolean.kt @@ -21,16 +21,25 @@ import android.content.Context import android.provider.Settings import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -fun Context.settingsGlobalBoolean(name: String, defaultValue: Boolean = false): - ReadWriteProperty<Any?, Boolean> = SettingsGlobalBooleanDelegate(this, name, defaultValue) +fun Context.settingsGlobalBoolean( + name: String, + defaultValue: Boolean = false, +): ReadWriteProperty<Any?, Boolean> = SettingsGlobalBooleanDelegate(this, name, defaultValue) fun Context.settingsGlobalBooleanFlow(name: String, defaultValue: Boolean = false): Flow<Boolean> { val value by settingsGlobalBoolean(name, defaultValue) - return settingsGlobalChangeFlow(name).map { value }.distinctUntilChanged() + return settingsGlobalChangeFlow(name) + .map { value } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.Default) } private class SettingsGlobalBooleanDelegate( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBoolean.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBoolean.kt index 25090a4140a4..d5f4ec264e43 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBoolean.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBoolean.kt @@ -22,16 +22,25 @@ import android.provider.Settings import com.android.settingslib.spaprivileged.database.contentChangeFlow import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -fun Context.settingsSecureBoolean(name: String, defaultValue: Boolean = false): - ReadWriteProperty<Any?, Boolean> = SettingsSecureBooleanDelegate(this, name, defaultValue) +fun Context.settingsSecureBoolean( + name: String, + defaultValue: Boolean = false, +): ReadWriteProperty<Any?, Boolean> = SettingsSecureBooleanDelegate(this, name, defaultValue) fun Context.settingsSecureBooleanFlow(name: String, defaultValue: Boolean = false): Flow<Boolean> { val value by settingsSecureBoolean(name, defaultValue) - return contentChangeFlow(Settings.Secure.getUriFor(name)).map { value }.distinctUntilChanged() + return contentChangeFlow(Settings.Secure.getUriFor(name)) + .map { value } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.Default) } private class SettingsSecureBooleanDelegate( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureString.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureString.kt new file mode 100644 index 000000000000..a70630819c0d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureString.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.spaprivileged.settingsprovider + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings +import com.android.settingslib.spaprivileged.database.contentChangeFlow +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +fun Context.settingsSecureString( + name: String, + defaultValue: String = "" +): ReadWriteProperty<Any?, String> = SettingsSecureStringDelegate(this, name, defaultValue) + +fun Context.settingsSecureStringFlow(name: String, defaultValue: String = ""): Flow<String> { + val value by settingsSecureString(name, defaultValue) + return contentChangeFlow(Settings.Secure.getUriFor(name)) + .map { value } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.Default) +} + +private class SettingsSecureStringDelegate( + context: Context, + private val name: String, + private val defaultValue: String = "", +) : ReadWriteProperty<Any?, String> { + + private val contentResolver: ContentResolver = context.contentResolver + + override fun getValue(thisRef: Any?, property: KProperty<*>): String = + Settings.Secure.getString(contentResolver, name) ?: defaultValue + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { + Settings.Secure.putString(contentResolver, name, value) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt index 2a04424d70bf..627b248e75b9 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spaprivileged.template.app import android.content.Context import android.content.pm.ApplicationInfo import android.os.Process +import android.os.UserHandle import androidx.compose.runtime.Composable import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider @@ -88,7 +89,8 @@ interface TogglePermissionAppListModel<T : AppRecord> { * * If true, the app gets all permissions, so the permission toggle always not changeable. */ -fun AppRecord.isSystemOrRootUid(): Boolean = app.uid in listOf(Process.SYSTEM_UID, Process.ROOT_UID) +fun AppRecord.isSystemOrRootUid(): Boolean = + UserHandle.getAppId(app.uid) in listOf(Process.SYSTEM_UID, Process.ROOT_UID) /** * Gets whether the permission on / off is changeable for the given app. diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt index dd7c0368bf4b..a59a724d9346 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt @@ -23,8 +23,8 @@ import android.content.IntentFilter import android.os.UserHandle import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.test.junit4.createComposeRule +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.testing.TestLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Rule diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt index c60ce41be87e..b1baa8601f28 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt @@ -31,6 +31,8 @@ import android.content.res.Resources import android.os.BadParcelableException import android.os.DeadObjectException import android.os.UserManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -76,9 +78,7 @@ class AppListRepositoryTest { } private val mockUserManager = mock<UserManager> { - on { getUserInfo(ADMIN_USER_ID) } doReturn UserInfo().apply { - flags = UserInfo.FLAG_ADMIN - } + on { getUserInfo(ADMIN_USER_ID) } doReturn UserInfo(0, "admin", UserInfo.FLAG_ADMIN) on { getProfileIdsWithDisabled(ADMIN_USER_ID) } doReturn intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID) } @@ -281,9 +281,9 @@ class AppListRepositoryTest { ) } + @EnableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX) @Test fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX) packageManager.stub { on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE) } @@ -297,9 +297,9 @@ class AppListRepositoryTest { assertThat(appList).containsExactly(NORMAL_APP) } + @DisableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX) @Test fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest { - mSetFlagsRule.disableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX) packageManager.stub { on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE) } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt new file mode 100644 index 000000000000..e3d182bb5ace --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.spaprivileged.settingsprovider + +import android.content.Context +import android.provider.Settings +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spa.testutils.toListWithTimeout +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SettingsSecureStringTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun getValue_returnCorrectValue() { + Settings.Secure.putString(context.contentResolver, TEST_NAME, VALUE) + + val value by context.settingsSecureString(TEST_NAME) + + assertThat(value).isEqualTo(VALUE) + } + + @Test + fun setValue_correctValueSet() { + var value by context.settingsSecureString(TEST_NAME) + + value = VALUE + + assertThat(Settings.Secure.getString(context.contentResolver, TEST_NAME)).isEqualTo(VALUE) + } + + @Test + fun settingsSecureStringFlow_valueNotChanged() = runBlocking { + var value by context.settingsSecureString(TEST_NAME) + value = VALUE + + val flow = context.settingsSecureStringFlow(TEST_NAME) + + assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(VALUE) + } + + @Test + fun settingsSecureStringFlow_collectAfterValueChanged_onlyKeepLatest() = runBlocking { + var value by context.settingsSecureString(TEST_NAME) + value = "" + + val flow = context.settingsSecureStringFlow(TEST_NAME) + value = VALUE + + assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(VALUE) + } + + @Test + fun settingsSecureStringFlow_collectBeforeValueChanged_getBoth() = runBlocking { + var value by context.settingsSecureString(TEST_NAME) + value = "" + + val listDeferred = async { context.settingsSecureStringFlow(TEST_NAME).toListWithTimeout() } + delay(100) + value = VALUE + + assertThat(listDeferred.await()).containsAtLeast("", VALUE).inOrder() + } + + private companion object { + const val TEST_NAME = "test_string_delegate" + const val VALUE = "value" + } +} diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 32557b979bf5..4ac3e671a378 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -79,3 +79,23 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_determining_spatial_audio_attributes_by_profile" + namespace: "cross_device_experiences" + description: "Use bluetooth profile connection policy to determine spatial audio attributes" + bug: "341005211" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "volume_panel_broadcast_fix" + namespace: "systemui" + description: "Make the volume panel's repository listen for the new ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED broadcast instead of ACTION_NOTIFICATION_POLICY_CHANGED" + bug: "347707024" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/res/drawable/ic_do_not_disturb_on_24dp.xml b/packages/SettingsLib/res/drawable/ic_do_not_disturb_on_24dp.xml new file mode 100644 index 000000000000..06c0d8c82518 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_do_not_disturb_on_24dp.xml @@ -0,0 +1,28 @@ +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8c0,-4.41 3.59,-8 8,-8c4.41,0 8,3.59 8,8C20,16.41 16.41,20 12,20z"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M7,11h10v2h-10z"/> +</vector> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 50f0c2ec4fcd..9da1d602cc4c 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -236,7 +236,7 @@ </string-array> <string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string> <string name="category_personal" msgid="6236798763159385225">"Peribadi"</string> - <string name="category_work" msgid="4014193632325996115">"Tempat Kerja"</string> + <string name="category_work" msgid="4014193632325996115">"Kerja"</string> <string name="category_private" msgid="4244892185452788977">"Persendirian"</string> <string name="category_clone" msgid="1554511758987195974">"Klon"</string> <string name="development_settings_title" msgid="140296922921597393">"Pilihan pembangun"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 363045ec1d83..27c386e52c8c 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -953,6 +953,18 @@ <string name="enable_verbose_vendor_logging">Enable verbose vendor logging</string> <!-- UI debug setting: enable verbose vendor logging summary [CHAR LIMIT=NONE] --> <string name="enable_verbose_vendor_logging_summary">Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery, and/or use more storage.</string> + <!-- UI debug setting: checkbox text to disable verbose vendor logging after one day [CHAR LIMIT=50] --> + <string name="enable_verbose_vendor_logging_checkbox">Disable after one day</string> + <!-- UI debug setting: verbose vendor logging notification title [CHAR LIMIT=60] --> + <string name="verbose_vendor_logging_notification_title">Verbose vendor logging has ended</string> + <!-- UI debug setting: verbose vendor logging notification summary [CHAR LIMIT=60] --> + <string name="verbose_vendor_logging_notification_summary">Enabled for one day</string> + <!-- UI debug setting: verbose vendor logging notification action text [CHAR LIMIT=60] --> + <string name="verbose_vendor_logging_notification_action">Enable for one more day</string> + <!-- UI debug setting: verbose vendor logging preference summary for disabling after one day [CHAR LIMIT=60] --> + <string name="verbose_vendor_logging_preference_summary_will_disable">Disables after one day</string> + <!-- UI debug setting: verbose vendor logging preference summary for leaving setting enabled indefinitely [CHAR LIMIT=60] --> + <string name="verbose_vendor_logging_preference_summary_on">Enabled indefinitely</string> <!-- UI debug setting: scaling factor for window animations [CHAR LIMIT=25] --> <string name="window_animation_scale_title">Window animation scale</string> @@ -1349,6 +1361,9 @@ <!-- Keywords for setting screen for controlling apps that can schedule alarms [CHAR LIMIT=100] --> <string name="keywords_alarms_and_reminders">schedule, alarm, reminder, clock</string> + <!-- Sound: Title for the Do not Disturb option and associated settings page. [CHAR LIMIT=50]--> + <string name="zen_mode_settings_title">Do Not Disturb</string> + <!-- Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] --> <string name="zen_mode_enable_dialog_turn_on">Turn on</string> <!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]--> @@ -1375,6 +1390,9 @@ <!-- Do not disturb: Duration option to always have DND on until it is manually turned off [CHAR LIMIT=60] --> <string name="zen_mode_forever">Until you turn off</string> + <!-- [CHAR LIMIT=50] Zen mode settings: placeholder for a Contact name when the name is empty --> + <string name="zen_mode_starred_contacts_empty_name">(No name)</string> + <!-- time label for event have that happened very recently [CHAR LIMIT=60] --> <string name="time_unit_just_now">Just now</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 1597a4b4e138..fc163ce79009 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -430,13 +430,21 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { return null; } + /** + * Retrieves the user ID of a managed profile associated with a specific user. + * + * <p>This method iterates over the users in the profile group associated with the given user ID + * and returns the ID of the user that is identified as a managed profile user. + * If no managed profile is found, it returns {@link UserHandle#USER_NULL}. + * + * @param context The context used to obtain the {@link UserManager} system service. + * @param userId The ID of the user for whom to find the managed profile. + * @return The user ID of the managed profile, or {@link UserHandle#USER_NULL} if none exists. + */ private static int getManagedProfileId(Context context, int userId) { UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); List<UserInfo> userProfiles = um.getProfiles(userId); for (UserInfo uInfo : userProfiles) { - if (uInfo.id == userId) { - continue; - } if (uInfo.isManagedProfile()) { return uInfo.id; } @@ -821,11 +829,11 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { } EnforcedAdmin admin = RestrictedLockUtils.getProfileOrDeviceOwner( - context, UserHandle.of(UserHandle.USER_SYSTEM)); + context, context.getUser()); if (admin != null) { return admin; } - int profileId = getManagedProfileId(context, UserHandle.USER_SYSTEM); + int profileId = getManagedProfileId(context, context.getUserId()); return RestrictedLockUtils.getProfileOrDeviceOwner(context, UserHandle.of(profileId)); } @@ -848,7 +856,7 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { if (admin != null) { return admin; } - int profileId = getManagedProfileId(context, UserHandle.USER_SYSTEM); + int profileId = getManagedProfileId(context, context.getUserId()); return RestrictedLockUtils.getProfileOrDeviceOwner(context, UserHandle.of(profileId)); } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index b7758de0e19c..4e1d8e38dfbd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -671,7 +671,7 @@ public abstract class InfoMediaManager { // MediaRoute2Info.getType was made public on API 34, but exists since API 30. @SuppressWarnings("NewApi") @VisibleForTesting - void addMediaDevice(MediaRoute2Info route, RoutingSessionInfo activeSession) { + void addMediaDevice(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo activeSession) { final int deviceType = route.getType(); MediaDevice mediaDevice = null; switch (deviceType) { @@ -711,8 +711,13 @@ public abstract class InfoMediaManager { case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: case TYPE_BLE_HEADSET: + if (route.getAddress() == null) { + Log.e(TAG, "Ignoring bluetooth route with no set address: " + route); + break; + } final BluetoothDevice device = - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress()); + BluetoothAdapter.getDefaultAdapter() + .getRemoteDevice(route.getAddress()); final CachedBluetoothDevice cachedDevice = mBluetoothManager.getCachedDeviceManager().findDevice(device); if (cachedDevice != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java new file mode 100644 index 000000000000..3f19830c9114 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.notification.modes; + +import static com.google.common.util.concurrent.Futures.immediateFuture; + +import static java.util.Objects.requireNonNull; + +import android.annotation.Nullable; +import android.app.AutomaticZenRule; +import android.content.Context; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.service.notification.SystemZenRules; +import android.text.TextUtils; +import android.util.Log; +import android.util.LruCache; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.content.res.AppCompatResources; + +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ZenIconLoader { + + private static final String TAG = "ZenIconLoader"; + + private static final Drawable MISSING = new ColorDrawable(); + + @Nullable // Until first usage + private static ZenIconLoader sInstance; + + private final LruCache<String, Drawable> mCache; + private final ListeningExecutorService mBackgroundExecutor; + + public static ZenIconLoader getInstance() { + if (sInstance == null) { + sInstance = new ZenIconLoader(); + } + return sInstance; + } + + private ZenIconLoader() { + this(Executors.newFixedThreadPool(4)); + } + + @VisibleForTesting + ZenIconLoader(ExecutorService backgroundExecutor) { + mCache = new LruCache<>(50); + mBackgroundExecutor = + MoreExecutors.listeningDecorator(backgroundExecutor); + } + + @NonNull + ListenableFuture<Drawable> getIcon(Context context, @NonNull AutomaticZenRule rule) { + if (rule.getIconResId() == 0) { + return Futures.immediateFuture(getFallbackIcon(context, rule.getType())); + } + + return FluentFuture.from(loadIcon(context, rule.getPackageName(), rule.getIconResId())) + .transform(icon -> + icon != null ? icon : getFallbackIcon(context, rule.getType()), + MoreExecutors.directExecutor()); + } + + @NonNull + private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context, String pkg, + int iconResId) { + String cacheKey = pkg + ":" + iconResId; + synchronized (mCache) { + Drawable cachedValue = mCache.get(cacheKey); + if (cachedValue != null) { + return immediateFuture(cachedValue != MISSING ? cachedValue : null); + } + } + + return FluentFuture.from(mBackgroundExecutor.submit(() -> { + if (TextUtils.isEmpty(pkg) || SystemZenRules.PACKAGE_ANDROID.equals(pkg)) { + return context.getDrawable(iconResId); + } else { + Context appContext = context.createPackageContext(pkg, 0); + Drawable appDrawable = AppCompatResources.getDrawable(appContext, iconResId); + return getMonochromeIconIfPresent(appDrawable); + } + })).catching(Exception.class, ex -> { + // If we cannot resolve the icon, then store MISSING in the cache below, so + // we don't try again. + Log.e(TAG, "Error while loading icon " + cacheKey, ex); + return null; + }, MoreExecutors.directExecutor()).transform(drawable -> { + synchronized (mCache) { + mCache.put(cacheKey, drawable != null ? drawable : MISSING); + } + return drawable; + }, MoreExecutors.directExecutor()); + } + + private static Drawable getFallbackIcon(Context context, int ruleType) { + int iconResIdFromType = switch (ruleType) { + case AutomaticZenRule.TYPE_UNKNOWN -> + com.android.internal.R.drawable.ic_zen_mode_type_unknown; + case AutomaticZenRule.TYPE_OTHER -> + com.android.internal.R.drawable.ic_zen_mode_type_other; + case AutomaticZenRule.TYPE_SCHEDULE_TIME -> + com.android.internal.R.drawable.ic_zen_mode_type_schedule_time; + case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> + com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar; + case AutomaticZenRule.TYPE_BEDTIME -> + com.android.internal.R.drawable.ic_zen_mode_type_bedtime; + case AutomaticZenRule.TYPE_DRIVING -> + com.android.internal.R.drawable.ic_zen_mode_type_driving; + case AutomaticZenRule.TYPE_IMMERSIVE -> + com.android.internal.R.drawable.ic_zen_mode_type_immersive; + case AutomaticZenRule.TYPE_THEATER -> + com.android.internal.R.drawable.ic_zen_mode_type_theater; + case AutomaticZenRule.TYPE_MANAGED -> + com.android.internal.R.drawable.ic_zen_mode_type_managed; + default -> com.android.internal.R.drawable.ic_zen_mode_type_unknown; + }; + return requireNonNull(context.getDrawable(iconResIdFromType)); + } + + private static Drawable getMonochromeIconIfPresent(Drawable icon) { + // For created rules, the app should've provided a monochrome Drawable. However, implicit + // rules have the app's icon, which is not -- but might have a monochrome layer. Thus + // we choose it, if present. + if (icon instanceof AdaptiveIconDrawable adaptiveIcon) { + if (adaptiveIcon.getMonochrome() != null) { + // Wrap with negative inset => scale icon (inspired from BaseIconFactory) + return new InsetDrawable(adaptiveIcon.getMonochrome(), + -2.0f * AdaptiveIconDrawable.getExtraInsetFraction()); + } + } + return icon; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java new file mode 100644 index 000000000000..33d39f000663 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.notification.modes; + +import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL; +import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; +import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent; +import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime; +import static android.service.notification.ZenModeConfig.tryParseEventConditionId; +import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId; + +import static com.google.common.base.Preconditions.checkState; + +import static java.util.Objects.requireNonNull; + +import android.annotation.SuppressLint; +import android.app.AutomaticZenRule; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.service.notification.SystemZenRules; +import android.service.notification.ZenDeviceEffects; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenPolicy; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settingslib.R; + +import com.google.common.base.Strings; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.Objects; + +/** + * Represents either an {@link AutomaticZenRule} or the manual DND rule in a unified way. + * + * <p>It also adapts other rule features that we don't want to expose in the UI, such as + * interruption filters other than {@code PRIORITY}, rules without specific icons, etc. + */ +public class ZenMode { + + private static final String TAG = "ZenMode"; + + static final String MANUAL_DND_MODE_ID = "manual_dnd"; + + // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy. + private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS = + new ZenPolicy.Builder() + .disallowAllSounds() + .allowAlarms(true) + .allowMedia(true) + .allowPriorityChannels(false) + .build(); + + // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy. + private static final ZenPolicy POLICY_INTERRUPTION_FILTER_NONE = + new ZenPolicy.Builder() + .disallowAllSounds() + .hideAllVisualEffects() + .allowPriorityChannels(false) + .build(); + + public enum Status { + ENABLED, + ENABLED_AND_ACTIVE, + DISABLED_BY_USER, + DISABLED_BY_OTHER + } + + private final String mId; + private final AutomaticZenRule mRule; + private final Status mStatus; + private final boolean mIsManualDnd; + + /** + * Initializes a {@link ZenMode}, mainly based on the information from the + * {@link AutomaticZenRule}. + * + * <p>Some pieces which are not part of the public API (such as whether the mode is currently + * active, or the reason it was disabled) are read from the {@link ZenModeConfig.ZenRule} -- + * see {@link #computeStatus}. + */ + public ZenMode(String id, @NonNull AutomaticZenRule rule, + @NonNull ZenModeConfig.ZenRule zenRuleExtraData) { + this(id, rule, computeStatus(zenRuleExtraData), false); + } + + private static Status computeStatus(@NonNull ZenModeConfig.ZenRule zenRuleExtraData) { + if (zenRuleExtraData.enabled) { + if (zenRuleExtraData.isAutomaticActive()) { + return Status.ENABLED_AND_ACTIVE; + } else { + return Status.ENABLED; + } + } else { + if (zenRuleExtraData.disabledOrigin == ZenModeConfig.UPDATE_ORIGIN_USER) { + return Status.DISABLED_BY_USER; + } else { + return Status.DISABLED_BY_OTHER; // by APP, SYSTEM, UNKNOWN. + } + } + } + + public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) { + return new ZenMode(MANUAL_DND_MODE_ID, manualRule, + isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true); + } + + private ZenMode(String id, @NonNull AutomaticZenRule rule, Status status, boolean isManualDnd) { + mId = id; + mRule = rule; + mStatus = status; + mIsManualDnd = isManualDnd; + } + + @NonNull + public String getId() { + return mId; + } + + @NonNull + public AutomaticZenRule getRule() { + return mRule; + } + + @NonNull + public String getName() { + return Strings.nullToEmpty(mRule.getName()); + } + + @NonNull + public Status getStatus() { + return mStatus; + } + + @AutomaticZenRule.Type + public int getType() { + return mRule.getType(); + } + + @Nullable + public String getTriggerDescription() { + return mRule.getTriggerDescription(); + } + + @NonNull + public ListenableFuture<Drawable> getIcon(@NonNull Context context, + @NonNull ZenIconLoader iconLoader) { + if (mIsManualDnd) { + return Futures.immediateFuture(requireNonNull( + context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp))); + } + + return iconLoader.getIcon(context, mRule); + } + + @NonNull + public ZenPolicy getPolicy() { + switch (mRule.getInterruptionFilter()) { + case INTERRUPTION_FILTER_PRIORITY: + case NotificationManager.INTERRUPTION_FILTER_ALL: + return requireNonNull(mRule.getZenPolicy()); + + case NotificationManager.INTERRUPTION_FILTER_ALARMS: + return POLICY_INTERRUPTION_FILTER_ALARMS; + + case NotificationManager.INTERRUPTION_FILTER_NONE: + return POLICY_INTERRUPTION_FILTER_NONE; + + case NotificationManager.INTERRUPTION_FILTER_UNKNOWN: + default: + Log.wtf(TAG, "Rule " + mId + " with unexpected interruptionFilter " + + mRule.getInterruptionFilter()); + return requireNonNull(mRule.getZenPolicy()); + } + } + + /** + * Updates the {@link ZenPolicy} of the associated {@link AutomaticZenRule} based on the + * supplied policy. In some cases this involves conversions, so that the following call + * to {@link #getPolicy} might return a different policy from the one supplied here. + */ + @SuppressLint("WrongConstant") + public void setPolicy(@NonNull ZenPolicy policy) { + ZenPolicy currentPolicy = getPolicy(); + if (currentPolicy.equals(policy)) { + return; + } + + if (mRule.getInterruptionFilter() == INTERRUPTION_FILTER_ALL) { + Log.wtf(TAG, "Able to change policy without filtering being enabled"); + } + + // If policy is customized from any of the "special" ones, make the rule PRIORITY. + if (mRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) { + mRule.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY); + } + mRule.setZenPolicy(policy); + } + + @NonNull + public ZenDeviceEffects getDeviceEffects() { + return mRule.getDeviceEffects() != null + ? mRule.getDeviceEffects() + : new ZenDeviceEffects.Builder().build(); + } + + public void setCustomModeConditionId(Context context, Uri conditionId) { + checkState(SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()), + "Trying to change condition of non-system-owned rule %s (to %s)", + mRule, conditionId); + + Uri oldCondition = mRule.getConditionId(); + mRule.setConditionId(conditionId); + + ZenModeConfig.ScheduleInfo scheduleInfo = tryParseScheduleConditionId(conditionId); + if (scheduleInfo != null) { + mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_TIME); + mRule.setOwner(ZenModeConfig.getScheduleConditionProvider()); + mRule.setTriggerDescription( + getTriggerDescriptionForScheduleTime(context, scheduleInfo)); + return; + } + + ZenModeConfig.EventInfo eventInfo = tryParseEventConditionId(conditionId); + if (eventInfo != null) { + mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR); + mRule.setOwner(ZenModeConfig.getEventConditionProvider()); + mRule.setTriggerDescription(getTriggerDescriptionForScheduleEvent(context, eventInfo)); + return; + } + + if (ZenModeConfig.isValidCustomManualConditionId(conditionId)) { + mRule.setType(AutomaticZenRule.TYPE_OTHER); + mRule.setOwner(ZenModeConfig.getCustomManualConditionProvider()); + mRule.setTriggerDescription(""); + return; + } + + Log.wtf(TAG, String.format( + "Changed condition of rule %s (%s -> %s) but cannot recognize which kind of " + + "condition it was!", + mRule, oldCondition, conditionId)); + } + + public boolean canEditName() { + return !isManualDnd(); + } + + public boolean canEditIcon() { + return !isManualDnd(); + } + + public boolean canBeDeleted() { + return !isManualDnd(); + } + + public boolean isManualDnd() { + return mIsManualDnd; + } + + public boolean isActive() { + return mStatus == Status.ENABLED_AND_ACTIVE; + } + + public boolean isSystemOwned() { + return SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()); + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ZenMode other + && mId.equals(other.mId) + && mRule.equals(other.mRule) + && mStatus.equals(other.mStatus) + && mIsManualDnd == other.mIsManualDnd; + } + + @Override + public int hashCode() { + return Objects.hash(mId, mRule, mStatus, mIsManualDnd); + } + + @Override + public String toString() { + return mId + " (" + mStatus + ") -> " + mRule; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java new file mode 100644 index 000000000000..5529da009329 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.notification.modes; + +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.AutomaticZenRule; +import android.app.NotificationManager; +import android.content.Context; +import android.net.Uri; +import android.provider.Settings; +import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.util.Log; + +import com.android.settingslib.R; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * Class used for Settings-NMS interactions related to Mode management. + * + * <p>This class converts {@link AutomaticZenRule} instances, as well as the manual zen mode, + * into the unified {@link ZenMode} format. + */ +public class ZenModesBackend { + + private static final String TAG = "ZenModeBackend"; + + @Nullable // Until first usage + private static ZenModesBackend sInstance; + + private final NotificationManager mNotificationManager; + + private final Context mContext; + + public static ZenModesBackend getInstance(Context context) { + if (sInstance == null) { + sInstance = new ZenModesBackend(context.getApplicationContext()); + } + return sInstance; + } + + ZenModesBackend(Context context) { + mContext = context; + mNotificationManager = context.getSystemService(NotificationManager.class); + } + + public List<ZenMode> getModes() { + Map<String, AutomaticZenRule> zenRules = mNotificationManager.getAutomaticZenRules(); + ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig(); + + ArrayList<ZenMode> modes = new ArrayList<>(); + modes.add(getManualDndMode(currentConfig)); + + for (Map.Entry<String, AutomaticZenRule> zenRuleEntry : zenRules.entrySet()) { + String ruleId = zenRuleEntry.getKey(); + ZenModeConfig.ZenRule extraData = currentConfig.automaticRules.get(ruleId); + if (extraData != null) { + modes.add(new ZenMode(ruleId, zenRuleEntry.getValue(), extraData)); + } else { + Log.w(TAG, "Found AZR " + zenRuleEntry.getValue() + + " but no corresponding entry in ZenModeConfig (" + currentConfig + + "). Skipping"); + } + } + + // Manual DND first, then alphabetically. + modes.sort(Comparator.comparing(ZenMode::isManualDnd).reversed() + .thenComparing(ZenMode::getName)); + + return modes; + } + + @Nullable + public ZenMode getMode(String id) { + ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig(); + if (ZenMode.MANUAL_DND_MODE_ID.equals(id)) { + return getManualDndMode(currentConfig); + } else { + AutomaticZenRule rule = mNotificationManager.getAutomaticZenRule(id); + ZenModeConfig.ZenRule extraData = currentConfig.automaticRules.get(id); + if (rule == null || extraData == null) { + return null; + } + return new ZenMode(id, rule, extraData); + } + } + + private ZenMode getManualDndMode(ZenModeConfig config) { + ZenModeConfig.ZenRule manualRule = config.manualRule; + // TODO: b/333682392 - Replace with final strings for name & trigger description + AutomaticZenRule manualDndRule = new AutomaticZenRule.Builder( + mContext.getString(R.string.zen_mode_settings_title), manualRule.conditionId) + .setType(manualRule.type) + .setZenPolicy(manualRule.zenPolicy) + .setDeviceEffects(manualRule.zenDeviceEffects) + .setManualInvocationAllowed(manualRule.allowManualInvocation) + .setConfigurationActivity(null) // No further settings + .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY) + .build(); + + return ZenMode.manualDndMode(manualDndRule, config != null && config.isManualActive()); + } + + public void updateMode(ZenMode mode) { + if (mode.isManualDnd()) { + try { + NotificationManager.Policy dndPolicy = + new ZenModeConfig().toNotificationPolicy(mode.getPolicy()); + mNotificationManager.setNotificationPolicy(dndPolicy, /* fromUser= */ true); + + mNotificationManager.setManualZenRuleDeviceEffects( + mode.getRule().getDeviceEffects()); + } catch (Exception e) { + Log.w(TAG, "Error updating manual mode", e); + } + } else { + mNotificationManager.updateAutomaticZenRule(mode.getId(), mode.getRule(), + /* fromUser= */ true); + } + } + + public void activateMode(ZenMode mode, @Nullable Duration forDuration) { + if (mode.isManualDnd()) { + Uri durationConditionId = null; + if (forDuration != null) { + durationConditionId = ZenModeConfig.toTimeCondition(mContext, + (int) forDuration.toMinutes(), ActivityManager.getCurrentUser(), true).id; + } + mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + durationConditionId, TAG, /* fromUser= */ true); + + } else { + if (forDuration != null) { + throw new IllegalArgumentException( + "Only the manual DND mode can be activated for a specific duration"); + } + mNotificationManager.setAutomaticZenRuleState(mode.getId(), + new Condition(mode.getRule().getConditionId(), "", Condition.STATE_TRUE, + Condition.SOURCE_USER_ACTION)); + } + } + + public void deactivateMode(ZenMode mode) { + if (mode.isManualDnd()) { + // When calling with fromUser=true this will not snooze other modes. + mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG, + /* fromUser= */ true); + } else { + // TODO: b/333527800 - This should (potentially) snooze the rule if it was active. + mNotificationManager.setAutomaticZenRuleState(mode.getId(), + new Condition(mode.getRule().getConditionId(), "", Condition.STATE_FALSE, + Condition.SOURCE_USER_ACTION)); + } + } + + public void removeMode(ZenMode mode) { + if (!mode.canBeDeleted()) { + throw new IllegalArgumentException("Mode " + mode + " cannot be deleted!"); + } + mNotificationManager.removeAutomaticZenRule(mode.getId(), /* fromUser= */ true); + } + + /** + * Creates a new custom mode with the provided {@code name}. The mode will be "manual" (i.e. + * not have a schedule), this can be later updated by the user in the mode settings page. + * + * @return the created mode. Only {@code null} if creation failed due to an internal error + */ + @Nullable + public ZenMode addCustomMode(String name) { + AutomaticZenRule rule = new AutomaticZenRule.Builder(name, + ZenModeConfig.toCustomManualConditionId()) + .setPackage(ZenModeConfig.getCustomManualConditionProvider().getPackageName()) + .setType(AutomaticZenRule.TYPE_OTHER) + .setOwner(ZenModeConfig.getCustomManualConditionProvider()) + .setManualInvocationAllowed(true) + .build(); + + String ruleId = mNotificationManager.addAutomaticZenRule(rule); + return getMode(ruleId); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt deleted file mode 100644 index a696f8c9c9c9..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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.settingslib.statusbar.notification.data.model - -import android.provider.Settings.Global - -/** Validating wrapper for [android.app.NotificationManager.getZenMode] values. */ -@JvmInline -value class ZenMode(val zenMode: Int) { - - init { - require(zenMode in supportedModes) { "Unsupported zenMode=$zenMode" } - } - - private companion object { - - val supportedModes = - listOf( - Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, - Global.ZEN_MODE_NO_INTERRUPTIONS, - Global.ZEN_MODE_ALARMS, - Global.ZEN_MODE_OFF, - ) - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeZenModeRepository.kt index a939ed14b7c1..775e2fc47b47 100644 --- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeZenModeRepository.kt @@ -18,19 +18,18 @@ package com.android.settingslib.statusbar.notification.data.repository import android.app.NotificationManager import android.provider.Settings -import com.android.settingslib.statusbar.notification.data.model.ZenMode import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepository { +class FakeZenModeRepository : ZenModeRepository { private val mutableNotificationPolicy = MutableStateFlow<NotificationManager.Policy?>(null) - override val notificationPolicy: StateFlow<NotificationManager.Policy?> + override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> get() = mutableNotificationPolicy.asStateFlow() - private val mutableZenMode = MutableStateFlow<ZenMode?>(ZenMode(Settings.Global.ZEN_MODE_OFF)) - override val zenMode: StateFlow<ZenMode?> + private val mutableZenMode = MutableStateFlow(Settings.Global.ZEN_MODE_OFF) + override val globalZenMode: StateFlow<Int> get() = mutableZenMode.asStateFlow() init { @@ -41,12 +40,12 @@ class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepositor mutableNotificationPolicy.value = policy } - fun updateZenMode(zenMode: ZenMode?) { + fun updateZenMode(zenMode: Int) { mutableZenMode.value = zenMode } } -fun FakeNotificationsSoundPolicyRepository.updateNotificationPolicy( +fun FakeZenModeRepository.updateNotificationPolicy( priorityCategories: Int = 0, priorityCallSenders: Int = NotificationManager.Policy.PRIORITY_SENDERS_ANY, priorityMessageSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE, diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/ZenModeRepository.kt index 0fb8c3f8bc6d..4d25237757ea 100644 --- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/ZenModeRepository.kt @@ -21,7 +21,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import com.android.settingslib.statusbar.notification.data.model.ZenMode +import com.android.settingslib.flags.Flags import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose @@ -37,21 +37,20 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Provides state of volume policy and restrictions imposed by notifications. */ -interface NotificationsSoundPolicyRepository { - - /** @see NotificationManager.getNotificationPolicy */ - val notificationPolicy: StateFlow<NotificationManager.Policy?> +interface ZenModeRepository { + /** @see NotificationManager.getConsolidatedNotificationPolicy */ + val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> /** @see NotificationManager.getZenMode */ - val zenMode: StateFlow<ZenMode?> + val globalZenMode: StateFlow<Int?> } -class NotificationsSoundPolicyRepositoryImpl( +class ZenModeRepositoryImpl( private val context: Context, private val notificationManager: NotificationManager, - scope: CoroutineScope, - backgroundCoroutineContext: CoroutineContext, -) : NotificationsSoundPolicyRepository { + val scope: CoroutineScope, + val backgroundCoroutineContext: CoroutineContext, +) : ZenModeRepository { private val notificationBroadcasts = callbackFlow { @@ -67,29 +66,45 @@ class NotificationsSoundPolicyRepositoryImpl( IntentFilter().apply { addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) - } - ) + if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi()) + addAction( + NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) + }) awaitClose { context.unregisterReceiver(receiver) } } - .shareIn( - started = SharingStarted.WhileSubscribed(), - scope = scope, - ) + .apply { + if (Flags.volumePanelBroadcastFix()) { + flowOn(backgroundCoroutineContext) + stateIn(scope, SharingStarted.WhileSubscribed(), null) + } else { + shareIn( + started = SharingStarted.WhileSubscribed(), + scope = scope, + ) + } + } - override val notificationPolicy: StateFlow<NotificationManager.Policy?> = - notificationBroadcasts - .filter { NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED == it } - .map { notificationManager.consolidatedNotificationPolicy } - .onStart { emit(notificationManager.consolidatedNotificationPolicy) } - .flowOn(backgroundCoroutineContext) - .stateIn(scope, SharingStarted.WhileSubscribed(), null) + override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> = + if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi()) + flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) { + notificationManager.consolidatedNotificationPolicy + } + else + flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) { + notificationManager.consolidatedNotificationPolicy + } + + override val globalZenMode: StateFlow<Int?> = + flowFromBroadcast(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) { + notificationManager.zenMode + } - override val zenMode: StateFlow<ZenMode?> = + private fun <T> flowFromBroadcast(intentAction: String, mapper: () -> T) = notificationBroadcasts - .filter { NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED == it } - .map { ZenMode(notificationManager.zenMode) } - .onStart { emit(ZenMode(notificationManager.zenMode)) } + .filter { intentAction == it } + .map { mapper() } + .onStart { emit(mapper()) } .flowOn(backgroundCoroutineContext) .stateIn(scope, SharingStarted.WhileSubscribed(), null) } diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt index 7719c4b75e9c..953c90d35d78 100644 --- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt @@ -20,8 +20,7 @@ import android.app.NotificationManager import android.media.AudioManager import android.provider.Settings import android.service.notification.ZenModeConfig -import com.android.settingslib.statusbar.notification.data.model.ZenMode -import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository +import com.android.settingslib.statusbar.notification.data.repository.ZenModeRepository import com.android.settingslib.volume.shared.model.AudioStream import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -30,17 +29,15 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map /** Determines notification sounds state and limitations. */ -class NotificationsSoundPolicyInteractor( - private val repository: NotificationsSoundPolicyRepository -) { +class NotificationsSoundPolicyInteractor(private val repository: ZenModeRepository) { /** @see NotificationManager.getNotificationPolicy */ - val notificationPolicy: StateFlow<NotificationManager.Policy?> - get() = repository.notificationPolicy + private val notificationPolicy: StateFlow<NotificationManager.Policy?> + get() = repository.consolidatedNotificationPolicy /** @see NotificationManager.getZenMode */ - val zenMode: StateFlow<ZenMode?> - get() = repository.zenMode + val zenMode: StateFlow<Int?> + get() = repository.globalZenMode /** Checks if [notificationPolicy] allows alarms. */ val areAlarmsAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowAlarms() } @@ -67,7 +64,7 @@ class NotificationsSoundPolicyInteractor( isRingerAllowed.filterNotNull(), isSystemAllowed.filterNotNull(), ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed, isSystemAllowed -> - when (zenMode.zenMode) { + when (zenMode) { // Everything is muted Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> return@combine true Settings.Global.ZEN_MODE_ALARMS -> diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt index 20b949f4a30f..8ec5ba193dba 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -20,6 +20,7 @@ import android.content.ContentResolver import android.database.ContentObserver import android.media.AudioDeviceInfo import android.media.AudioManager +import android.media.AudioManager.AudioDeviceCategory import android.media.AudioManager.OnCommunicationDeviceChangedListener import android.provider.Settings import androidx.concurrent.futures.DirectExecutor @@ -85,6 +86,10 @@ interface AudioRepository { suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) + + /** Gets audio device category. */ + @AudioDeviceCategory + suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int } class AudioRepositoryImpl( @@ -211,6 +216,13 @@ class AudioRepositoryImpl( withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value } } + @AudioDeviceCategory + override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int { + return withContext(backgroundCoroutineContext) { + audioManager.getBluetoothAudioDeviceCategory(bluetoothAddress) + } + } + private fun getMinVolume(stream: AudioStream): Int = try { audioManager.getStreamMinVolume(stream.value) diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/ZenModeRepositoryTest.kt index dfc4c0a847c0..688bebb79384 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepositoryTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/ZenModeRepositoryTest.kt @@ -20,10 +20,12 @@ import android.app.NotificationManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.provider.Settings.Global import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.statusbar.notification.data.model.ZenMode +import com.android.settingslib.flags.Flags import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -45,13 +47,15 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) @SmallTest -class NotificationsSoundPolicyRepositoryTest { +class ZenModeRepositoryTest { @Mock private lateinit var context: Context + @Mock private lateinit var notificationManager: NotificationManager + @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver> - private lateinit var underTest: NotificationsSoundPolicyRepository + private lateinit var underTest: ZenModeRepository private val testScope: TestScope = TestScope() @@ -60,7 +64,7 @@ class NotificationsSoundPolicyRepositoryTest { MockitoAnnotations.initMocks(this) underTest = - NotificationsSoundPolicyRepositoryImpl( + ZenModeRepositoryImpl( context, notificationManager, testScope.backgroundScope, @@ -68,15 +72,18 @@ class NotificationsSoundPolicyRepositoryTest { ) } + @DisableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX) @Test - fun policyChanges_repositoryEmits() { + fun consolidatedPolicyChanges_repositoryEmits_flagsOff() { testScope.runTest { val values = mutableListOf<NotificationManager.Policy?>() - `when`(notificationManager.notificationPolicy).thenReturn(testPolicy1) - underTest.notificationPolicy.onEach { values.add(it) }.launchIn(backgroundScope) + `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1) + underTest.consolidatedNotificationPolicy + .onEach { values.add(it) } + .launchIn(backgroundScope) runCurrent() - `when`(notificationManager.notificationPolicy).thenReturn(testPolicy2) + `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy2) triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) runCurrent() @@ -86,12 +93,33 @@ class NotificationsSoundPolicyRepositoryTest { } } + @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX) + @Test + fun consolidatedPolicyChanges_repositoryEmits_flagsOn() { + testScope.runTest { + val values = mutableListOf<NotificationManager.Policy?>() + `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1) + underTest.consolidatedNotificationPolicy + .onEach { values.add(it) } + .launchIn(backgroundScope) + runCurrent() + + `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy2) + triggerIntent(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) + runCurrent() + + assertThat(values) + .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2)) + .inOrder() + } + } + @Test fun zenModeChanges_repositoryEmits() { testScope.runTest { - val values = mutableListOf<ZenMode?>() + val values = mutableListOf<Int?>() `when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_OFF) - underTest.zenMode.onEach { values.add(it) }.launchIn(backgroundScope) + underTest.globalZenMode.onEach { values.add(it) }.launchIn(backgroundScope) runCurrent() `when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_ALARMS) @@ -100,8 +128,7 @@ class NotificationsSoundPolicyRepositoryTest { assertThat(values) .containsExactlyElementsIn( - listOf(null, ZenMode(Global.ZEN_MODE_OFF), ZenMode(Global.ZEN_MODE_ALARMS)) - ) + listOf(null, Global.ZEN_MODE_OFF, Global.ZEN_MODE_ALARMS)) .inOrder() } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt index 683759db95f9..844dc12ee911 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt @@ -247,6 +247,19 @@ class AudioRepositoryTest { } } + @Test + fun getBluetoothAudioDeviceCategory() { + testScope.runTest { + `when`(audioManager.getBluetoothAudioDeviceCategory("12:34:56:78")).thenReturn( + AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES) + + val category = underTest.getBluetoothAudioDeviceCategory("12:34:56:78") + runCurrent() + + assertThat(category).isEqualTo(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES) + } + } + private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) { verify(audioManager) .addOnCommunicationDeviceChangedListener( diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenIconLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenIconLoaderTest.java new file mode 100644 index 000000000000..20461e37a786 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenIconLoaderTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.notification.modes; + +import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AutomaticZenRule; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.service.notification.ZenPolicy; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class ZenIconLoaderTest { + + private Context mContext; + private ZenIconLoader mLoader; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mLoader = new ZenIconLoader(MoreExecutors.newDirectExecutorService()); + } + + @Test + public void getIcon_systemOwnedRuleWithIcon_loads() throws Exception { + AutomaticZenRule systemRule = newRuleBuilder() + .setPackage("android") + .setIconResId(android.R.drawable.ic_media_play) + .build(); + + ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, systemRule); + assertThat(loadFuture.isDone()).isTrue(); + assertThat(loadFuture.get()).isNotNull(); + } + + @Test + public void getIcon_ruleWithoutSpecificIcon_loadsFallback() throws Exception { + AutomaticZenRule rule = newRuleBuilder() + .setType(AutomaticZenRule.TYPE_DRIVING) + .setPackage("com.blah") + .build(); + + ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, rule); + assertThat(loadFuture.isDone()).isTrue(); + assertThat(loadFuture.get()).isNotNull(); + } + + @Test + public void getIcon_ruleWithAppIconWithLoadFailure_loadsFallback() throws Exception { + AutomaticZenRule rule = newRuleBuilder() + .setType(AutomaticZenRule.TYPE_DRIVING) + .setPackage("com.blah") + .setIconResId(-123456) + .build(); + + ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, rule); + assertThat(loadFuture.get()).isNotNull(); + } + + private static AutomaticZenRule.Builder newRuleBuilder() { + return new AutomaticZenRule.Builder("Driving", Uri.parse("drive")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().build()); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java new file mode 100644 index 000000000000..32cdb98ddb4c --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.notification.modes; + +import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS; +import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE; +import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AutomaticZenRule; +import android.net.Uri; +import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenPolicy; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class ZenModeTest { + + private static final ZenPolicy ZEN_POLICY = new ZenPolicy.Builder().allowAllSounds().build(); + + private static final AutomaticZenRule ZEN_RULE = + new AutomaticZenRule.Builder("Driving", Uri.parse("drive")) + .setPackage("com.some.driving.thing") + .setType(AutomaticZenRule.TYPE_DRIVING) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(ZEN_POLICY) + .build(); + + @Test + public void testBasicMethods() { + ZenMode zenMode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, true)); + + assertThat(zenMode.getId()).isEqualTo("id"); + assertThat(zenMode.getRule()).isEqualTo(ZEN_RULE); + assertThat(zenMode.isManualDnd()).isFalse(); + assertThat(zenMode.canBeDeleted()).isTrue(); + assertThat(zenMode.isActive()).isTrue(); + + ZenMode manualMode = ZenMode.manualDndMode(ZEN_RULE, false); + assertThat(manualMode.getId()).isEqualTo(ZenMode.MANUAL_DND_MODE_ID); + assertThat(manualMode.isManualDnd()).isTrue(); + assertThat(manualMode.canBeDeleted()).isFalse(); + assertThat(manualMode.isActive()).isFalse(); + } + + @Test + public void constructor_enabledRule_statusEnabled() { + AutomaticZenRule azr = new AutomaticZenRule.Builder(ZEN_RULE).setEnabled(true).build(); + ZenModeConfig.ZenRule configZenRule = zenConfigRuleFor(azr, false); + + ZenMode mode = new ZenMode("id", azr, configZenRule); + assertThat(mode.getStatus()).isEqualTo(ZenMode.Status.ENABLED); + assertThat(mode.isActive()).isFalse(); + } + + @Test + public void constructor_activeRule_statusActive() { + AutomaticZenRule azr = new AutomaticZenRule.Builder(ZEN_RULE).setEnabled(true).build(); + ZenModeConfig.ZenRule configZenRule = zenConfigRuleFor(azr, true); + + ZenMode mode = new ZenMode("id", azr, configZenRule); + assertThat(mode.getStatus()).isEqualTo(ZenMode.Status.ENABLED_AND_ACTIVE); + assertThat(mode.isActive()).isTrue(); + } + + @Test + public void constructor_disabledRuleByUser_statusDisabledByUser() { + AutomaticZenRule azr = new AutomaticZenRule.Builder(ZEN_RULE).setEnabled(false).build(); + ZenModeConfig.ZenRule configZenRule = zenConfigRuleFor(azr, false); + configZenRule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_USER; + + ZenMode mode = new ZenMode("id", azr, configZenRule); + assertThat(mode.getStatus()).isEqualTo(ZenMode.Status.DISABLED_BY_USER); + } + + @Test + public void constructor_disabledRuleByOther_statusDisabledByOther() { + AutomaticZenRule azr = new AutomaticZenRule.Builder(ZEN_RULE).setEnabled(false).build(); + ZenModeConfig.ZenRule configZenRule = zenConfigRuleFor(azr, false); + configZenRule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_APP; + + ZenMode mode = new ZenMode("id", azr, configZenRule); + assertThat(mode.getStatus()).isEqualTo(ZenMode.Status.DISABLED_BY_OTHER); + } + + @Test + public void getPolicy_interruptionFilterPriority_returnsZenPolicy() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(ZEN_POLICY) + .build(); + ZenMode zenMode = new ZenMode("id", azr, zenConfigRuleFor(azr, false)); + + assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY); + } + + @Test + public void getPolicy_interruptionFilterAlarms_returnsPolicyAllowingAlarms() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .setZenPolicy(ZEN_POLICY) // should be ignored + .build(); + ZenMode zenMode = new ZenMode("id", azr, zenConfigRuleFor(azr, false)); + + assertThat(zenMode.getPolicy()).isEqualTo( + new ZenPolicy.Builder() + .disallowAllSounds() + .allowAlarms(true) + .allowMedia(true) + .allowPriorityChannels(false) + .build()); + } + + @Test + public void getPolicy_interruptionFilterNone_returnsPolicyAllowingNothing() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_NONE) + .setZenPolicy(ZEN_POLICY) // should be ignored + .build(); + ZenMode zenMode = new ZenMode("id", azr, zenConfigRuleFor(azr, false)); + + assertThat(zenMode.getPolicy()).isEqualTo( + new ZenPolicy.Builder() + .disallowAllSounds() + .hideAllVisualEffects() + .allowPriorityChannels(false) + .build()); + } + + @Test + public void setPolicy_setsInterruptionFilterPriority() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .build(); + ZenMode zenMode = new ZenMode("id", azr, zenConfigRuleFor(azr, false)); + + zenMode.setPolicy(ZEN_POLICY); + + assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo( + INTERRUPTION_FILTER_PRIORITY); + assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY); + assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY); + } + + private static ZenModeConfig.ZenRule zenConfigRuleFor(AutomaticZenRule azr, boolean isActive) { + ZenModeConfig.ZenRule zenRule = new ZenModeConfig.ZenRule(); + zenRule.pkg = azr.getPackageName(); + zenRule.conditionId = azr.getConditionId(); + zenRule.enabled = azr.isEnabled(); + if (isActive) { + zenRule.condition = new Condition(azr.getConditionId(), "active", Condition.STATE_TRUE); + } + return zenRule; + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java new file mode 100644 index 000000000000..00c7ae3e97d7 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.notification.modes; + +import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; +import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; +import static android.provider.Settings.Global.ZEN_MODE_OFF; +import static android.service.notification.Condition.SOURCE_UNKNOWN; +import static android.service.notification.Condition.STATE_FALSE; +import static android.service.notification.Condition.STATE_TRUE; +import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy; +import static android.service.notification.ZenPolicy.STATE_ALLOW; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AutomaticZenRule; +import android.app.Flags; +import android.app.NotificationManager; +import android.app.NotificationManager.Policy; +import android.content.Context; +import android.net.Uri; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; +import android.service.notification.Condition; +import android.service.notification.ZenDeviceEffects; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenPolicy; + +import com.android.settingslib.R; + +import com.google.common.collect.ImmutableMap; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; + +import java.time.Duration; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@EnableFlags(Flags.FLAG_MODES_UI) +public class ZenModesBackendTest { + + private static final String ZEN_RULE_ID = "rule"; + private static final AutomaticZenRule ZEN_RULE = + new AutomaticZenRule.Builder("Driving", Uri.parse("drive")) + .setType(AutomaticZenRule.TYPE_DRIVING) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build()) + .build(); + + private static final AutomaticZenRule MANUAL_DND_RULE = + new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build()) + .build(); + + @Mock + private NotificationManager mNm; + + private Context mContext; + private ZenModesBackend mBackend; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( + SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + + // Helper methods to add active/inactive rule state to a config. Returns a copy. + private static ZenModeConfig configWithManualRule(ZenModeConfig base, boolean active) { + ZenModeConfig out = base.copy(); + + if (active) { + out.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + out.manualRule.condition = + new Condition(out.manualRule.conditionId, "", STATE_TRUE, SOURCE_UNKNOWN); + } else { + out.manualRule.zenMode = ZEN_MODE_OFF; + out.manualRule.condition = + new Condition(out.manualRule.conditionId, "", STATE_FALSE, SOURCE_UNKNOWN); + } + return out; + } + + private static ZenModeConfig configWithRule(ZenModeConfig base, String ruleId, + AutomaticZenRule rule, boolean active) { + ZenModeConfig out = base.copy(); + out.automaticRules.put(ruleId, zenConfigRuleForRule(ruleId, rule, active)); + return out; + } + + private static ZenModeConfig.ZenRule zenConfigRuleForRule(String id, AutomaticZenRule azr, + boolean active) { + // Note that there are many other fields of zenRule, but here we only set the ones + // relevant to determining whether or not it is active. + ZenModeConfig.ZenRule zenRule = new ZenModeConfig.ZenRule(); + zenRule.id = id; + zenRule.pkg = "package"; + zenRule.enabled = azr.isEnabled(); + zenRule.snoozing = false; + zenRule.conditionId = azr.getConditionId(); + zenRule.condition = new Condition(azr.getConditionId(), "", + active ? Condition.STATE_TRUE : Condition.STATE_FALSE, + Condition.SOURCE_USER_ACTION); + return zenRule; + } + + private static ZenMode newZenMode(String id, AutomaticZenRule azr, boolean active) { + return new ZenMode(id, azr, zenConfigRuleForRule(id, azr, active)); + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + + mContext = RuntimeEnvironment.application; + mBackend = new ZenModesBackend(mContext); + + // Default catch-all case with no data. This isn't realistic, but tests below that rely + // on the config to get data on rules active will create those individually. + when(mNm.getZenModeConfig()).thenReturn(new ZenModeConfig()); + } + + @Test + public void getModes_containsManualDndAndZenRules() { + AutomaticZenRule rule2 = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed")) + .setType(AutomaticZenRule.TYPE_BEDTIME) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build()) + .build(); + Policy dndPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS, + Policy.PRIORITY_SENDERS_CONTACTS, Policy.PRIORITY_SENDERS_CONTACTS); + when(mNm.getAutomaticZenRules()).thenReturn( + ImmutableMap.of("rule1", ZEN_RULE, "rule2", rule2)); + + ZenModeConfig config = new ZenModeConfig(); + config.applyNotificationPolicy(dndPolicy); + config = configWithRule(config, "rule1", ZEN_RULE, false); + config = configWithRule(config, "rule2", rule2, false); + assertThat(config.manualRule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_ALLOW); + when(mNm.getZenModeConfig()).thenReturn(config); + + List<ZenMode> modes = mBackend.getModes(); + + // all modes exist, but none of them are currently active + assertThat(modes).containsExactly( + ZenMode.manualDndMode( + new AutomaticZenRule.Builder( + mContext.getString(R.string.zen_mode_settings_title), + Uri.EMPTY) + .setType(AutomaticZenRule.TYPE_OTHER) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(notificationPolicyToZenPolicy(dndPolicy)) + .setManualInvocationAllowed(true) + .build(), + false), + newZenMode("rule2", rule2, false), + newZenMode("rule1", ZEN_RULE, false)) + .inOrder(); + } + + @Test + public void getMode_manualDnd_returnsMode() { + Policy dndPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS, + Policy.PRIORITY_SENDERS_CONTACTS, Policy.PRIORITY_SENDERS_CONTACTS); + ZenModeConfig config = new ZenModeConfig(); + config.applyNotificationPolicy(dndPolicy); + when(mNm.getZenModeConfig()).thenReturn(config); + + ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID); + + assertThat(mode).isEqualTo( + ZenMode.manualDndMode( + new AutomaticZenRule.Builder( + mContext.getString(R.string.zen_mode_settings_title), Uri.EMPTY) + .setType(AutomaticZenRule.TYPE_OTHER) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(notificationPolicyToZenPolicy(dndPolicy)) + .setManualInvocationAllowed(true) + .build(), false)); + } + + @Test + public void getMode_zenRule_returnsMode() { + when(mNm.getAutomaticZenRule(eq(ZEN_RULE_ID))).thenReturn(ZEN_RULE); + when(mNm.getZenModeConfig()).thenReturn( + configWithRule(new ZenModeConfig(), ZEN_RULE_ID, ZEN_RULE, false)); + + ZenMode mode = mBackend.getMode(ZEN_RULE_ID); + + assertThat(mode).isEqualTo(newZenMode(ZEN_RULE_ID, ZEN_RULE, false)); + } + + @Test + public void getMode_missingRule_returnsNull() { + when(mNm.getAutomaticZenRule(any())).thenReturn(null); + + ZenMode mode = mBackend.getMode(ZEN_RULE_ID); + + assertThat(mode).isNull(); + verify(mNm).getAutomaticZenRule(eq(ZEN_RULE_ID)); + } + + @Test + public void getMode_manualDnd_returnsCorrectActiveState() { + // Set up a base config with an active rule to make sure we're looking at the correct info + ZenModeConfig configWithActiveRule = configWithRule(new ZenModeConfig(), ZEN_RULE_ID, + ZEN_RULE, true); + + // Equivalent to disallowAllSounds() + Policy dndPolicy = new Policy(0, 0, 0); + configWithActiveRule.applyNotificationPolicy(dndPolicy); + when(mNm.getZenModeConfig()).thenReturn(configWithActiveRule); + + ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID); + + // By default, manual rule is inactive + assertThat(mode).isNotNull(); + assertThat(mode.isActive()).isFalse(); + + // Now the returned config will represent the manual rule being active + when(mNm.getZenModeConfig()).thenReturn(configWithManualRule(configWithActiveRule, true)); + ZenMode activeMode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID); + assertThat(activeMode).isNotNull(); + assertThat(activeMode.isActive()).isTrue(); + } + + @Test + public void getMode_zenRule_returnsCorrectActiveState() { + // Set up a base config that has an active manual rule and "rule2", to make sure we're + // looking at the correct rule's info. + ZenModeConfig configWithActiveRules = configWithRule( + configWithManualRule(new ZenModeConfig(), true), // active manual rule + "rule2", ZEN_RULE, true); // active rule 2 + + when(mNm.getAutomaticZenRule(eq(ZEN_RULE_ID))).thenReturn(ZEN_RULE); + when(mNm.getZenModeConfig()).thenReturn( + configWithRule(configWithActiveRules, ZEN_RULE_ID, ZEN_RULE, false)); + + // Round 1: the current config should indicate that the rule is not active + ZenMode mode = mBackend.getMode(ZEN_RULE_ID); + assertThat(mode).isNotNull(); + assertThat(mode.isActive()).isFalse(); + + when(mNm.getZenModeConfig()).thenReturn( + configWithRule(configWithActiveRules, ZEN_RULE_ID, ZEN_RULE, true)); + ZenMode activeMode = mBackend.getMode(ZEN_RULE_ID); + assertThat(activeMode).isNotNull(); + assertThat(activeMode.isActive()).isTrue(); + } + + @Test + public void updateMode_manualDnd_setsDeviceEffects() throws Exception { + ZenMode manualDnd = ZenMode.manualDndMode( + new AutomaticZenRule.Builder("DND", Uri.EMPTY) + .setZenPolicy(new ZenPolicy()) + .setDeviceEffects(new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .build()) + .build(), false); + + mBackend.updateMode(manualDnd); + + verify(mNm).setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .build()); + } + + @Test + public void updateMode_manualDnd_setsNotificationPolicy() { + ZenMode manualDnd = ZenMode.manualDndMode( + new AutomaticZenRule.Builder("DND", Uri.EMPTY) + .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build()) + .build(), false); + + mBackend.updateMode(manualDnd); + + verify(mNm).setNotificationPolicy(eq(new ZenModeConfig().toNotificationPolicy( + new ZenPolicy.Builder().allowAllSounds().build())), eq(true)); + } + + @Test + public void updateMode_zenRule_updatesRule() { + ZenMode ruleMode = newZenMode("rule", ZEN_RULE, false); + + mBackend.updateMode(ruleMode); + + verify(mNm).updateAutomaticZenRule(eq("rule"), eq(ZEN_RULE), eq(true)); + } + + @Test + public void activateMode_manualDnd_setsZenModeImportant() { + mBackend.activateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false), null); + + verify(mNm).setZenMode(eq(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), + any(), eq(true)); + } + + @Test + public void activateMode_manualDndWithDuration_setsZenModeImportantWithCondition() { + mBackend.activateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false), + Duration.ofMinutes(30)); + + verify(mNm).setZenMode(eq(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS), + eq(ZenModeConfig.toTimeCondition(mContext, 30, 0, true).id), + any(), + eq(true)); + } + + @Test + public void activateMode_zenRule_setsRuleStateActive() { + mBackend.activateMode(newZenMode(ZEN_RULE_ID, ZEN_RULE, false), null); + + verify(mNm).setAutomaticZenRuleState(eq(ZEN_RULE_ID), + eq(new Condition(ZEN_RULE.getConditionId(), "", Condition.STATE_TRUE, + Condition.SOURCE_USER_ACTION))); + } + + @Test + public void activateMode_zenRuleWithDuration_fails() { + assertThrows(IllegalArgumentException.class, + () -> mBackend.activateMode(newZenMode(ZEN_RULE_ID, ZEN_RULE, false), + Duration.ofMinutes(30))); + } + + @Test + public void deactivateMode_manualDnd_setsZenModeOff() { + mBackend.deactivateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, true)); + + verify(mNm).setZenMode(eq(ZEN_MODE_OFF), eq(null), any(), eq(true)); + } + + @Test + public void deactivateMode_zenRule_setsRuleStateInactive() { + mBackend.deactivateMode(newZenMode(ZEN_RULE_ID, ZEN_RULE, false)); + + verify(mNm).setAutomaticZenRuleState(eq(ZEN_RULE_ID), + eq(new Condition(ZEN_RULE.getConditionId(), "", Condition.STATE_FALSE, + Condition.SOURCE_USER_ACTION))); + } + + @Test + public void removeMode_zenRule_deletesRule() { + mBackend.removeMode(newZenMode(ZEN_RULE_ID, ZEN_RULE, false)); + + verify(mNm).removeAutomaticZenRule(ZEN_RULE_ID, true); + } + + @Test + public void removeMode_manualDnd_fails() { + assertThrows(IllegalArgumentException.class, + () -> mBackend.removeMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false))); + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 9f2ab69acd31..8d02dbd3df4d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -279,5 +279,6 @@ public class SecureSettings { Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, + Settings.Secure.MANDATORY_BIOMETRICS, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index cb7ac457d991..7169cf7708eb 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -438,5 +438,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR); VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR); VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR); + VALIDATORS.put(Secure.MANDATORY_BIOMETRICS, new InclusiveIntegerRangeValidator(0, 1)); } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index fde7c2caca06..bd7c9a0e1e55 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -98,13 +98,66 @@ filegroup { ], } -// Tests where robolectric failed at runtime. (go/multivalent-tests) +// Tests where robolectric failed at runtime. (go/central-multivalent) filegroup { name: "SystemUI-tests-broken-robofiles-run", srcs: [ + "tests/src/**/systemui/ExpandHelperTest.java", + "tests/src/**/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java", + "tests/src/**/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java", + "tests/src/**/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java", + "tests/src/**/systemui/screenshot/appclips/AppClipsActivityTest.java", + "tests/src/**/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java", + "tests/src/**/systemui/screenshot/appclips/AppClipsViewModelTest.java", + "tests/src/**/systemui/appops/AppOpsControllerTest.java", + "tests/src/**/systemui/biometrics/BiometricNotificationServiceTest.java", + "tests/src/**/systemui/bluetooth/BroadcastDialogDelegateTest.java", + "tests/src/**/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java", + "tests/src/**/systemui/communal/data/backup/CommunalBackupHelperTest.kt", + "tests/src/**/systemui/controls/ui/ControlsPopupMenuTest.kt", + "tests/src/**/systemui/classifier/DistanceClassifierTest.java", + "tests/src/**/systemui/doze/DozeScreenBrightnessTest.java", + "tests/src/**/systemui/doze/DozeSensorsTest.java", + "tests/src/**/systemui/doze/DozeTriggersTest.java", + "tests/src/**/systemui/classifier/FalsingDataProviderTest.java", + "tests/src/**/systemui/screenshot/ImageExporterTest.java", + "tests/src/**/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt", + "tests/src/**/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt", + "tests/src/**/systemui/logcat/LogAccessDialogActivityTest.java", + "tests/src/**/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt", + "tests/src/**/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt", + "tests/src/**/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java", + "tests/src/**/systemui/accessibility/floatingmenu/MenuViewLayerTest.java", + "tests/src/**/systemui/accessibility/floatingmenu/MenuViewTest.java", + "tests/src/**/systemui/classifier/PointerCountClassifierTest.java", + "tests/src/**/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt", + "tests/src/**/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java", + "tests/src/**/systemui/screenrecord/RecordingControllerTest.java", + "tests/src/**/systemui/screenshot/RequestProcessorTest.kt", + "tests/src/**/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt", + "tests/src/**/systemui/screenshot/SaveImageInBackgroundTaskTest.kt", + "tests/src/**/systemui/screenshot/scroll/ScrollCaptureClientTest.java", + "tests/src/**/systemui/accessibility/SecureSettingsContentObserverTest.java", + "tests/src/**/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt", + "tests/src/**/systemui/qs/external/TileServicesTest.java", + "tests/src/**/systemui/ambient/touch/TouchMonitorTest.java", + "tests/src/**/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java", + "tests/src/**/systemui/accessibility/WindowMagnificationSettingsTest.java", + "tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt", + "tests/src/**/systemui/CameraProtectionLoaderImplTest.kt", + "tests/src/**/systemui/DependencyTest.java", + "tests/src/**/systemui/InitControllerTest.java", + "tests/src/**/systemui/SliceBroadcastRelayHandlerTest.java", + "tests/src/**/systemui/SystemUIApplicationTest.kt", + "tests/src/**/systemui/SysUICutoutProviderTest.kt", + "tests/src/**/keyguard/ActiveUnlockConfigTest.kt", + "tests/src/**/keyguard/AdminSecondaryLockScreenControllerTest.java", + "tests/src/**/keyguard/KeyguardClockAccessibilityDelegateTest.java", + "tests/src/**/keyguard/KeyguardStatusViewControllerTest.java", "tests/src/**/systemui/accessibility/AccessibilityButtonModeObserverTest.java", "tests/src/**/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java", "tests/src/**/systemui/accessibility/FullscreenMagnificationControllerTest.java", + "tests/src/**/systemui/accessibility/MagnificationTest.java", "tests/src/**/systemui/accessibility/WindowMagnificationAnimationControllerTest.java", "tests/src/**/systemui/animation/FontInterpolatorTest.kt", "tests/src/**/systemui/animation/TextAnimatorTest.kt", @@ -160,6 +213,7 @@ filegroup { "tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java", "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt", "tests/src/**/systemui/statusbar/notification/AssistantFeedbackControllerTest.java", + "tests/src/**/systemui/statusbar/notification/PropertyAnimatorTest.java", "tests/src/**/systemui/statusbar/notification/collection/NotifCollectionTest.java", "tests/src/**/systemui/statusbar/notification/collection/NotificationEntryTest.java", "tests/src/**/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt", diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index d201071620e4..08614540c330 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -73,6 +73,13 @@ flag { } flag { + name: "redesign_magnification_window_size" + namespace: "accessibility" + description: "Redesigns the window magnification magnifier sizes provided in the settings panel." + bug: "288056772" +} + +flag { name: "save_and_restore_magnification_settings_buttons" namespace: "accessibility" description: "Saves the selected button status in magnification settings and restore the status when revisiting the same smallest screen DP." diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 1cbf67ee0d0e..0ccaf181a389 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -421,10 +421,13 @@ flag { } flag { - name: "fast_unlock_transition" + name: "faster_unlock_transition" namespace: "systemui" description: "Faster wallpaper unlock transition" bug: "298186160" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -864,6 +867,13 @@ flag { } flag { + name: "keyboard_touchpad_contextual_education" + namespace: "systemui" + description: "Allow showing education for physical keyboard and touchpad" + bug: "317496783" +} + +flag { name: "dream_overlay_bouncer_swipe_direction_filtering" namespace: "systemui" description: "do not initiate bouncer swipe when the direction is opposite of the expansion" @@ -884,16 +894,6 @@ flag { } flag { - name: "shade_collapse_activity_launch_fix" - namespace: "systemui" - description: "Avoid collapsing the shade on activity launch if it is already collapsed, as this causes a flicker." - bug: "331591373" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "slice_broadcast_relay_in_background" namespace: "systemui" description: "Move handling of slice broadcast relay broadcasts to background threads" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index c4659cfc146b..7c2f7fe51d67 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -59,9 +59,11 @@ import com.android.systemui.communal.ui.compose.Dimensions.SlideOffsetY import com.android.systemui.communal.ui.compose.extensions.allowGestures import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION import com.android.systemui.res.R import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource +import kotlin.time.DurationUnit object Communal { object Elements { @@ -91,6 +93,10 @@ val sceneTransitions = transitions { spec = tween(durationMillis = 250) fade(AllElements) } + to(CommunalScenes.Blank, key = CommunalTransitionKeys.SimpleFade) { + spec = tween(durationMillis = TO_GONE_DURATION.toInt(DurationUnit.MILLISECONDS)) + fade(AllElements) + } to(CommunalScenes.Communal) { spec = tween(durationMillis = 1000) translate(Communal.Elements.Grid, Edge.Right) @@ -226,10 +232,14 @@ fun CommunalContainer( scene( CommunalScenes.Communal, - userActions = - mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank) + userActions = mapOf(Swipe(SwipeDirection.Right) to CommunalScenes.Blank) ) { - CommunalScene(backgroundType, colors, content) + CommunalScene( + backgroundType = backgroundType, + colors = colors, + content = content, + modifier = Modifier.horizontalNestedScrollToScene(), + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 18085ab29135..7d829208ee89 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -29,6 +29,7 @@ import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines +import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection import com.android.systemui.statusbar.phone.SystemUIDialogFactory import javax.inject.Inject @@ -41,8 +42,10 @@ constructor( private val interactionHandler: SmartspaceInteractionHandler, private val dialogFactory: SystemUIDialogFactory, private val lockSection: LockSection, + private val bottomAreaSection: BottomAreaSection, private val ambientStatusBarSection: AmbientStatusBarSection, ) { + @Composable fun SceneScope.Content(modifier: Modifier = Modifier) { Layout( @@ -65,10 +68,16 @@ constructor( modifier = Modifier.element(Communal.Elements.LockIcon) ) } + with(bottomAreaSection) { + IndicationArea( + Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth() + ) + } } ) { measurables, constraints -> val communalGridMeasurable = measurables[0] val lockIconMeasurable = measurables[1] + val bottomAreaMeasurable = measurables[2] val noMinConstraints = constraints.copy( @@ -85,6 +94,13 @@ constructor( bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], ) + val bottomAreaPlaceable = + bottomAreaMeasurable.measure( + noMinConstraints.copy( + maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) + ) + ) + val communalGridPlaceable = communalGridMeasurable.measure( noMinConstraints.copy(maxHeight = lockIconBounds.top) @@ -99,6 +115,10 @@ constructor( x = lockIconBounds.left, y = lockIconBounds.top, ) + bottomAreaPlaceable.place( + x = 0, + y = constraints.maxHeight - bottomAreaPlaceable.height, + ) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 927890e9d6af..9ea435edbb0f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -49,13 +49,16 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan @@ -75,12 +78,15 @@ import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonColors import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State @@ -124,7 +130,6 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.times import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup @@ -153,7 +158,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory import kotlinx.coroutines.launch -@OptIn(ExperimentalComposeUiApi::class) +@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class) @Composable fun CommunalHub( modifier: Modifier = Modifier, @@ -379,15 +384,32 @@ fun CommunalHub( ) } - // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving - // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge - // swipe back to the blank scene. - Spacer( - Modifier.height(Dimensions.GridHeight) - .align(Alignment.CenterStart) - .width(Dimensions.Spacing) - .pointerInput(Unit) {} - ) + if (viewModel is CommunalEditModeViewModel) { + val showBottomSheet by viewModel.showDisclaimer.collectAsStateWithLifecycle(false) + + if (showBottomSheet) { + val scope = rememberCoroutineScope() + val sheetState = rememberModalBottomSheetState() + val colors = LocalAndroidColorScheme.current + + ModalBottomSheet( + onDismissRequest = viewModel::onDisclaimerDismissed, + sheetState = sheetState, + dragHandle = null, + containerColor = colors.surfaceContainer, + ) { + DisclaimerBottomSheetContent { + scope + .launch { sheetState.hide() } + .invokeOnCompletion { + if (!sheetState.isVisible) { + viewModel.onDisclaimerDismissed() + } + } + } + } + } + } } } @@ -399,6 +421,47 @@ private fun onMotionEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } +@Composable +private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) { + val colors = LocalAndroidColorScheme.current + + Column( + modifier = Modifier.fillMaxWidth().padding(horizontal = 32.dp, vertical = 24.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = Icons.Outlined.Widgets, + contentDescription = null, + tint = colors.primary, + modifier = Modifier.size(32.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.communal_widgets_disclaimer_title), + style = MaterialTheme.typography.headlineMedium, + color = colors.onSurface, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.communal_widgets_disclaimer_text), + color = colors.onSurfaceVariant, + ) + Button( + modifier = + Modifier.padding(horizontal = 26.dp, vertical = 16.dp) + .widthIn(min = 200.dp) + .heightIn(min = 56.dp), + onClick = { onButtonClicked() } + ) { + Text( + stringResource(R.string.communal_widgets_disclaimer_button), + style = MaterialTheme.typography.labelLarge, + ) + } + } +} + /** * Observes communal content and scrolls to any added or updated live content, e.g. a new media * session is started, or a paused timer is resumed. @@ -612,10 +675,14 @@ private fun Toolbar( removeEnabled: Boolean, onRemoveClicked: () -> Unit, setToolbarSize: (toolbarSize: IntSize) -> Unit, - setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit, + setRemoveButtonCoordinates: (coordinates: LayoutCoordinates?) -> Unit, onOpenWidgetPicker: () -> Unit, onEditDone: () -> Unit ) { + if (!removeEnabled) { + // Clear any existing coordinates when remove is not enabled. + setRemoveButtonCoordinates(null) + } val removeButtonAlpha: Float by animateFloatAsState( targetValue = if (removeEnabled) 1f else 0.5f, @@ -655,7 +722,13 @@ private fun Toolbar( contentPadding = Dimensions.ButtonPadding, modifier = Modifier.graphicsLayer { alpha = removeButtonAlpha } - .onGloballyPositioned { setRemoveButtonCoordinates(it) } + .onGloballyPositioned { + // It's possible for this callback to fire after remove has been + // disabled. Check enabled state before setting. + if (removeEnabled) { + setRemoveButtonCoordinates(it) + } + } ) { Row( horizontalArrangement = @@ -921,7 +994,8 @@ private fun CtaTileInViewModeContent( shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp) ) { Column( - modifier = Modifier.fillMaxSize().padding(vertical = 38.dp, horizontal = 70.dp), + modifier = Modifier.fillMaxSize().padding(vertical = 32.dp, horizontal = 50.dp), + verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Icon( @@ -932,41 +1006,43 @@ private fun CtaTileInViewModeContent( Spacer(modifier = Modifier.size(6.dp)) Text( text = stringResource(R.string.cta_label_to_edit_widget), - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge, + fontSize = nonScalableTextSize(22.dp), + lineHeight = nonScalableTextSize(28.dp), ) - Spacer(modifier = Modifier.size(20.dp)) + Spacer(modifier = Modifier.size(16.dp)) Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().height(56.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally), ) { OutlinedButton( + modifier = Modifier.fillMaxHeight(), colors = ButtonDefaults.buttonColors( contentColor = colors.onPrimary, ), border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer), - contentPadding = Dimensions.ButtonPadding, + contentPadding = PaddingValues(26.dp, 8.dp), onClick = viewModel::onDismissCtaTile, ) { Text( text = stringResource(R.string.cta_tile_button_to_dismiss), - fontSize = 12.sp, + fontSize = nonScalableTextSize(14.dp), ) } - Spacer(modifier = Modifier.size(14.dp)) Button( + modifier = Modifier.fillMaxHeight(), colors = ButtonDefaults.buttonColors( containerColor = colors.primaryContainer, contentColor = colors.onPrimaryContainer, ), - contentPadding = Dimensions.ButtonPadding, + contentPadding = PaddingValues(26.dp, 8.dp), onClick = viewModel::onOpenWidgetEditor ) { Text( text = stringResource(R.string.cta_tile_button_to_open_widget_editor), - fontSize = 12.sp, + fontSize = nonScalableTextSize(14.dp), ) } } @@ -1279,6 +1355,13 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl } /** + * Text size converted from dp value to the equivalent sp value using the current screen density, + * ensuring it does not scale with the font size setting. + */ +@Composable +private fun nonScalableTextSize(sizeInDp: Dp) = with(LocalDensity.current) { sizeInDp.toSp() } + +/** * Returns the `contentPadding` of the grid. Use the vertical padding to push the grid content area * below the toolbar and let the grid take the max size. This ensures the item can be dragged * outside the grid over the toolbar, without part of it getting clipped by the container. diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt index 97d5b41000de..86639fa02a92 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.DpSize @@ -183,7 +184,7 @@ constructor( indicationController: KeyguardIndicationController, modifier: Modifier = Modifier, ) { - val (disposable, setDisposable) = mutableStateOf<DisposableHandle?>(null) + val (disposable, setDisposable) = remember { mutableStateOf<DisposableHandle?>(null) } AndroidView( factory = { context -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 45a83933a7ce..6805888214c2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars @@ -65,6 +66,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf @@ -89,8 +91,10 @@ object Notifications { object Elements { val NotificationScrim = ElementKey("NotificationScrim") val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder") - val HeadsUpNotificationPlaceholder = ElementKey("HeadsUpNotificationPlaceholder") + val HeadsUpNotificationPlaceholder = + ElementKey("HeadsUpNotificationPlaceholder", scenePicker = LowestZIndexScenePicker) val ShelfSpace = ElementKey("ShelfSpace") + val NotificationStackCutoffGuideline = ElementKey("NotificationStackCutoffGuideline") } // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be @@ -112,10 +116,10 @@ fun SceneScope.HeadsUpNotificationSpace( modifier: Modifier = Modifier, isPeekFromBottom: Boolean = false, ) { - Element( - Notifications.Elements.HeadsUpNotificationPlaceholder, + Box( modifier = modifier + .element(Notifications.Elements.HeadsUpNotificationPlaceholder) .fillMaxWidth() .notificationHeadsUpHeight(stackScrollView) .debugBackground(viewModel, DEBUG_HUN_COLOR) @@ -129,9 +133,7 @@ fun SceneScope.HeadsUpNotificationSpace( // Note: boundsInWindow doesn't scroll off the screen stackScrollView.setHeadsUpTop(boundsInWindow.top) } - ) { - content {} - } + ) } /** Adds the space where notification stack should appear in the scene. */ @@ -155,6 +157,11 @@ fun SceneScope.ConstrainedNotificationStack( viewModel = viewModel, modifier = Modifier.align(Alignment.TopCenter), ) + NotificationStackCutoffGuideline( + stackScrollView = stackScrollView, + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter), + ) } } @@ -171,6 +178,7 @@ fun SceneScope.NotificationScrollingStack( shouldPunchHoleBehindScrim: Boolean, shouldFillMaxSize: Boolean = true, shouldReserveSpaceForNavBar: Boolean = true, + shouldIncludeHeadsUpSpace: Boolean = true, shadeMode: ShadeMode, modifier: Modifier = Modifier, ) { @@ -187,8 +195,12 @@ fun SceneScope.NotificationScrollingStack( viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false) val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f) - val navBarHeight = - with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() } + val navBarHeightPx = + with(density) { + WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx().toInt() + } + val bottomPaddingPx = if (shouldReserveSpaceForNavBar) navBarHeightPx else 0 + val screenHeight = LocalRawScreenHeight.current /** @@ -352,14 +364,13 @@ fun SceneScope.NotificationScrollingStack( } .verticalScroll(scrollState) .fillMaxWidth() - .notificationStackHeight( - view = stackScrollView, - padding = if (shouldReserveSpaceForNavBar) navBarHeight.toInt() else 0 - ) + .notificationStackHeight(view = stackScrollView, padding = bottomPaddingPx) .onSizeChanged { size -> stackHeight.intValue = size.height }, ) } - HeadsUpNotificationSpace(stackScrollView = stackScrollView, viewModel = viewModel) + if (shouldIncludeHeadsUpSpace) { + HeadsUpNotificationSpace(stackScrollView = stackScrollView, viewModel = viewModel) + } } } @@ -395,6 +406,30 @@ fun SceneScope.NotificationShelfSpace( ) } +/** + * A 0 height horizontal spacer to be placed at the bottom-most position in the current scene, where + * the notification contents (stack, footer, shelf) should be drawn. + */ +@Composable +fun SceneScope.NotificationStackCutoffGuideline( + stackScrollView: NotificationScrollView, + viewModel: NotificationsPlaceholderViewModel, + modifier: Modifier = Modifier, +) { + Spacer( + modifier = + modifier + .element(key = Notifications.Elements.NotificationStackCutoffGuideline) + .fillMaxWidth() + .height(0.dp) + .onGloballyPositioned { coordinates -> + val positionY = coordinates.positionInWindow().y + debugLog(viewModel) { "STACK cutoff onGloballyPositioned: y=$positionY" } + stackScrollView.setStackCutoff(positionY) + } + ) +} + @Composable private fun SceneScope.NotificationPlaceholder( stackScrollView: NotificationScrollView, @@ -417,7 +452,6 @@ private fun SceneScope.NotificationPlaceholder( } // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not stackScrollView.setStackTop(positionInWindow.y) - stackScrollView.setStackBottom(positionInWindow.y + coordinates.size.height) } ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt index f62a28c94763..db98bc8fff9c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope @@ -75,7 +74,6 @@ constructor( OverlayShade( modifier = modifier, viewModel = overlayShadeViewModel, - panelAlignment = Alignment.TopEnd, lockscreenContent = lockscreenContent, ) { Column { @@ -98,6 +96,12 @@ constructor( shadeMode = ShadeMode.Dual, modifier = Modifier.fillMaxWidth(), ) + + // Communicates the bottom position of the drawable area within the shade to NSSL. + NotificationStackCutoffGuideline( + stackScrollView = stackScrollView.get(), + viewModel = notificationsPlaceholderViewModel, + ) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 1b49b6702275..0b57151e971d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -42,6 +42,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState @@ -62,6 +64,7 @@ import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope @@ -82,6 +85,8 @@ import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.dagger.MediaModule import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace +import com.android.systemui.notifications.ui.composable.NotificationScrollingStack +import com.android.systemui.notifications.ui.composable.NotificationStackCutoffGuideline import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.InQS @@ -90,6 +95,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader import com.android.systemui.shade.ui.composable.Shade @@ -102,6 +108,7 @@ import com.android.systemui.statusbar.phone.ui.TintedIconManager import dagger.Lazy import javax.inject.Inject import javax.inject.Named +import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn @@ -400,5 +407,24 @@ private fun SceneScope.QuickSettingsScene( modifier = Modifier.align(Alignment.BottomCenter), isPeekFromBottom = true, ) + NotificationScrollingStack( + shadeSession = shadeSession, + stackScrollView = notificationStackScrollView, + viewModel = notificationsPlaceholderViewModel, + maxScrimTop = { screenHeight }, + shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, + shouldIncludeHeadsUpSpace = false, + shadeMode = ShadeMode.Single, + modifier = + Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }, + ) + NotificationStackCutoffGuideline( + stackScrollView = notificationStackScrollView, + viewModel = viewModel.notifications, + modifier = + Modifier.align(Alignment.BottomCenter).navigationBarsPadding().offset { + IntOffset(x = 0, y = screenHeight.roundToInt()) + } + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt index a0d6be9c68dc..c066ae526cbe 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt @@ -82,7 +82,6 @@ constructor( ) { OverlayShade( viewModel = viewModel.overlayShadeViewModel, - panelAlignment = Alignment.TopEnd, lockscreenContent = lockscreenContent, modifier = modifier, ) { @@ -155,6 +154,7 @@ private fun QuickSettingsLayout( viewModel = viewModel.tileGridViewModel, modifier = Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), + viewModel.editModeViewModel::startEditing, ) Button( onClick = { viewModel.editModeViewModel.startEditing() }, @@ -169,7 +169,7 @@ object QuickSettingsShade { object Dimensions { val Padding = 16.dp val BrightnessSliderHeight = 64.dp - val GridMaxHeight = 400.dp + val GridMaxHeight = 800.dp } object Transitions { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index 5dc833be91bb..e433d32e350b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -1,10 +1,12 @@ package com.android.systemui.scene.ui.composable import androidx.compose.foundation.gestures.Orientation +import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.transitions import com.android.systemui.bouncer.ui.composable.Bouncer import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition @@ -42,31 +44,28 @@ val SceneContainerTransitions = transitions { // Scene transitions from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() } - from(Scenes.Gone, to = Scenes.NotificationsShade) { goneToNotificationsShadeTransition() } - from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() } - from( - Scenes.Gone, - to = Scenes.Shade, - key = ToSplitShade, - ) { - goneToSplitShadeTransition() + from(Scenes.Gone, to = Scenes.NotificationsShade) { + goneToNotificationsShadeTransition(Edge.Top) + } + from(Scenes.Gone, to = Scenes.NotificationsShade, key = OpenBottomShade) { + goneToNotificationsShadeTransition(Edge.Bottom) + } + from(Scenes.Gone, to = Scenes.QuickSettingsShade) { + goneToQuickSettingsShadeTransition(Edge.Top) } - from( - Scenes.Gone, - to = Scenes.Shade, - key = SlightlyFasterShadeCollapse, - ) { + from(Scenes.Gone, to = Scenes.QuickSettingsShade, key = OpenBottomShade) { + goneToQuickSettingsShadeTransition(Edge.Bottom) + } + from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() } + from(Scenes.Gone, to = Scenes.Shade, key = ToSplitShade) { goneToSplitShadeTransition() } + from(Scenes.Gone, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) { goneToShadeTransition(durationScale = 0.9) } from(Scenes.Gone, to = Scenes.QuickSettings) { goneToQuickSettingsTransition() } - from( - Scenes.Gone, - to = Scenes.QuickSettings, - key = SlightlyFasterShadeCollapse, - ) { + from(Scenes.Gone, to = Scenes.QuickSettings, key = SlightlyFasterShadeCollapse) { goneToQuickSettingsTransition(durationScale = 0.9) } - from(Scenes.Gone, to = Scenes.QuickSettingsShade) { goneToQuickSettingsShadeTransition() } + from(Scenes.Lockscreen, to = Scenes.Bouncer) { lockscreenToBouncerTransition() } from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() } from(Scenes.Lockscreen, to = Scenes.NotificationsShade) { @@ -76,18 +75,10 @@ val SceneContainerTransitions = transitions { lockscreenToQuickSettingsShadeTransition() } from(Scenes.Lockscreen, to = Scenes.Shade) { lockscreenToShadeTransition() } - from( - Scenes.Lockscreen, - to = Scenes.Shade, - key = ToSplitShade, - ) { + from(Scenes.Lockscreen, to = Scenes.Shade, key = ToSplitShade) { lockscreenToSplitShadeTransition() } - from( - Scenes.Lockscreen, - to = Scenes.Shade, - key = SlightlyFasterShadeCollapse, - ) { + from(Scenes.Lockscreen, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) { lockscreenToShadeTransition(durationScale = 0.9) } from(Scenes.Lockscreen, to = Scenes.QuickSettings) { lockscreenToQuickSettingsTransition() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt index 48ec198a790a..fb41374bfba8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt @@ -16,10 +16,12 @@ package com.android.systemui.scene.ui.composable.transitions +import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder fun TransitionBuilder.goneToNotificationsShadeTransition( + edge: Edge = Edge.Top, durationScale: Double = 1.0, ) { - toNotificationsShadeTransition(durationScale) + toNotificationsShadeTransition(edge, durationScale) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt index 225ca4ebdb91..8a03e29e6377 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt @@ -16,10 +16,12 @@ package com.android.systemui.scene.ui.composable.transitions +import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder fun TransitionBuilder.goneToQuickSettingsShadeTransition( + edge: Edge = Edge.Top, durationScale: Double = 1.0, ) { - toQuickSettingsShadeTransition(durationScale) + toQuickSettingsShadeTransition(edge, durationScale) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt index 372e4a1af482..02664c1cc91e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt @@ -21,5 +21,5 @@ import com.android.compose.animation.scene.TransitionBuilder fun TransitionBuilder.lockscreenToNotificationsShadeTransition( durationScale: Double = 1.0, ) { - toNotificationsShadeTransition(durationScale) + toNotificationsShadeTransition(durationScale = durationScale) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt index ce24f5ed6592..19aa3a7197d2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt @@ -16,10 +16,11 @@ package com.android.systemui.scene.ui.composable.transitions +import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder fun TransitionBuilder.lockscreenToQuickSettingsShadeTransition( durationScale: Double = 1.0, ) { - toQuickSettingsShadeTransition(durationScale) + toQuickSettingsShadeTransition(Edge.Top, durationScale) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt index 6b3b7602050d..05949b20fde2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt @@ -32,6 +32,11 @@ import com.android.systemui.shade.ui.composable.ShadeHeader import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.toNotificationsShadeTransition( + /** + * The edge where the shade will animate from. This is statically determined (i.e. doesn't + * change during runtime). + */ + edge: Edge = Edge.Top, durationScale: Double = 1.0, ) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) @@ -50,7 +55,7 @@ fun TransitionBuilder.toNotificationsShadeTransition( } } - translate(OverlayShade.Elements.Panel, Edge.Top) + translate(OverlayShade.Elements.Panel, edge) fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt index ec2f14f34dba..9d13647bc43f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt @@ -19,17 +19,15 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance -import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.composable.Shade import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.toQuickSettingsShadeTransition( + edge: Edge = Edge.Top, durationScale: Double = 1.0, ) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) @@ -38,17 +36,9 @@ fun TransitionBuilder.toQuickSettingsShadeTransition( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, ) - distance = - object : UserActionDistance { - override fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, - orientation: Orientation, - ): Float { - return fromSceneSize.height.toFloat() * 2 / 3f - } - } + distance = UserActionDistance { fromSceneSize, _ -> fromSceneSize.height.toFloat() * 2 / 3f } - translate(OverlayShade.Elements.Panel, Edge.Top) + translate(OverlayShade.Elements.Panel, edge) fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index c189d73e2db5..a94ebc8f86d2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -54,6 +54,7 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shared.model.ShadeAlignment import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel import com.android.systemui.util.kotlin.getOrNull import dagger.Lazy @@ -63,7 +64,6 @@ import java.util.Optional @Composable fun SceneScope.OverlayShade( viewModel: OverlayShadeViewModel, - panelAlignment: Alignment, lockscreenContent: Lazy<Optional<LockscreenContent>>, modifier: Modifier = Modifier, content: @Composable () -> Unit, @@ -82,7 +82,12 @@ fun SceneScope.OverlayShade( Box( modifier = Modifier.fillMaxSize().panelPadding(), - contentAlignment = panelAlignment, + contentAlignment = + if (viewModel.panelAlignment == ShadeAlignment.Top) { + Alignment.TopEnd + } else { + Alignment.BottomEnd + }, ) { Panel( modifier = Modifier.element(OverlayShade.Elements.Panel).panelSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index ec81e23ae46b..edef5fb2760a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState @@ -82,6 +83,7 @@ import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL import com.android.systemui.notifications.ui.composable.NotificationScrollingStack +import com.android.systemui.notifications.ui.composable.NotificationStackCutoffGuideline import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility import com.android.systemui.qs.ui.composable.BrightnessMirror import com.android.systemui.qs.ui.composable.QSMediaMeasurePolicy @@ -350,6 +352,11 @@ private fun SceneScope.SingleShade( notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt()) } } + NotificationStackCutoffGuideline( + stackScrollView = notificationStackScrollView, + viewModel = viewModel.notifications, + modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding() + ) } } @@ -529,6 +536,7 @@ private fun SceneScope.SplitShade( viewModel = viewModel.notifications, maxScrimTop = { 0f }, shouldPunchHoleBehindScrim = false, + shouldReserveSpaceForNavBar = false, shadeMode = ShadeMode.Split, modifier = Modifier.weight(1f) @@ -538,5 +546,10 @@ private fun SceneScope.SplitShade( ) } } + NotificationStackCutoffGuideline( + stackScrollView = notificationStackScrollView, + viewModel = viewModel.notifications, + modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding() + ) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index c2dd80375d5a..ea740a8d8de4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -48,8 +48,15 @@ internal fun CoroutineScope.animateToScene( } return when (transitionState) { - is TransitionState.Idle -> - animate(layoutState, target, transitionKey, isInitiatedByUserInput = false) + is TransitionState.Idle -> { + animate( + layoutState, + target, + transitionKey, + isInitiatedByUserInput = false, + replacedTransition = null, + ) + } is TransitionState.Transition -> { val isInitiatedByUserInput = transitionState.isInitiatedByUserInput @@ -79,6 +86,7 @@ internal fun CoroutineScope.animateToScene( isInitiatedByUserInput, initialProgress = progress, initialVelocity = transitionState.progressVelocity, + replacedTransition = transitionState, ) } } else if (transitionState.fromScene == target) { @@ -101,6 +109,7 @@ internal fun CoroutineScope.animateToScene( initialProgress = progress, initialVelocity = transitionState.progressVelocity, reversed = true, + replacedTransition = transitionState, ) } } else { @@ -137,6 +146,7 @@ internal fun CoroutineScope.animateToScene( isInitiatedByUserInput, fromScene = animateFrom, chain = chain, + replacedTransition = null, ) } } @@ -148,6 +158,7 @@ private fun CoroutineScope.animate( targetScene: SceneKey, transitionKey: TransitionKey?, isInitiatedByUserInput: Boolean, + replacedTransition: TransitionState.Transition?, initialProgress: Float = 0f, initialVelocity: Float = 0f, reversed: Boolean = false, @@ -164,6 +175,7 @@ private fun CoroutineScope.animate( currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = false, + replacedTransition = replacedTransition, ) } else { OneOffTransition( @@ -173,6 +185,7 @@ private fun CoroutineScope.animate( currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = false, + replacedTransition = replacedTransition, ) } @@ -214,7 +227,8 @@ private class OneOffTransition( override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, override val isUserInputOngoing: Boolean, -) : TransitionState.Transition(fromScene, toScene) { + replacedTransition: TransitionState.Transition?, +) : TransitionState.Transition(fromScene, toScene, replacedTransition) { /** * The animatable used to animate this transition. * diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index bb170246263f..e8fdfc8da3bc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job import kotlinx.coroutines.launch -interface DraggableHandler { +internal interface DraggableHandler { /** * Start a drag in the given [startedPosition], with the given [overSlop] and number of * [pointersDown]. @@ -51,7 +51,7 @@ interface DraggableHandler { * The [DragController] provides control over the transition between two scenes through the [onDrag] * and [onStop] methods. */ -interface DragController { +internal interface DragController { /** Drag the current scene by [delta] pixels. */ fun onDrag(delta: Float) @@ -395,10 +395,11 @@ private class DragControllerImpl( if ( distance != DistanceUnspecified && shouldCommitSwipe( - offset, - distance, - velocity, + offset = offset, + distance = distance, + velocity = velocity, wasCommitted = swipeTransition._currentScene == toScene, + requiresFullDistanceSwipe = swipeTransition.requiresFullDistanceSwipe, ) ) { targetScene = toScene @@ -472,7 +473,12 @@ private class DragControllerImpl( distance: Float, velocity: Float, wasCommitted: Boolean, + requiresFullDistanceSwipe: Boolean, ): Boolean { + if (requiresFullDistanceSwipe && !wasCommitted) { + return offset / distance >= 1f + } + fun isCloserToTarget(): Boolean { return (offset - distance).absoluteValue < offset.absoluteValue } @@ -530,6 +536,8 @@ private fun SwipeTransition( userActionDistanceScope = layoutImpl.userActionDistanceScope, orientation = orientation, isUpOrLeft = isUpOrLeft, + requiresFullDistanceSwipe = result.requiresFullDistanceSwipe, + replacedTransition = null, ) } @@ -543,7 +551,10 @@ private fun SwipeTransition(old: SwipeTransition): SwipeTransition { _toScene = old._toScene, userActionDistanceScope = old.userActionDistanceScope, orientation = old.orientation, - isUpOrLeft = old.isUpOrLeft + isUpOrLeft = old.isUpOrLeft, + lastDistance = old.lastDistance, + requiresFullDistanceSwipe = old.requiresFullDistanceSwipe, + replacedTransition = old, ) .apply { _currentScene = old._currentScene @@ -561,8 +572,11 @@ private class SwipeTransition( val userActionDistanceScope: UserActionDistanceScope, override val orientation: Orientation, override val isUpOrLeft: Boolean, + val requiresFullDistanceSwipe: Boolean, + replacedTransition: SwipeTransition?, + var lastDistance: Float = DistanceUnspecified, ) : - TransitionState.Transition(_fromScene.key, _toScene.key), + TransitionState.Transition(_fromScene.key, _toScene.key, replacedTransition), TransitionState.HasOverscrollProperties { var _currentScene by mutableStateOf(_fromScene) override val currentScene: SceneKey @@ -620,8 +634,6 @@ private class SwipeTransition( get() = distance().absoluteValue } - private var lastDistance = DistanceUnspecified - /** Whether [TransitionState.Transition.finish] was called on this transition. */ var isFinishing = false private set diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 09d11b7988b9..69124c1f6dad 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -306,7 +306,6 @@ internal class ElementNode( return layout(placeable.width, placeable.height) { place(transition, placeable) } } - @OptIn(ExperimentalComposeUiApi::class) private fun Placeable.PlacementScope.place( transition: TransitionState.Transition?, placeable: Placeable, @@ -496,6 +495,10 @@ private fun prepareInterruption( transition: TransitionState.Transition, previousTransition: TransitionState.Transition, ) { + if (transition.replacedTransition == previousTransition) { + return + } + val sceneStates = element.sceneStates fun updatedSceneState(key: SceneKey): Element.SceneState? { return sceneStates[key]?.also { it.selfUpdateValuesBeforeInterruption() } @@ -561,10 +564,20 @@ private fun reconcileStates( } private fun Element.SceneState.selfUpdateValuesBeforeInterruption() { - offsetBeforeInterruption = lastOffset sizeBeforeInterruption = lastSize - scaleBeforeInterruption = lastScale - alphaBeforeInterruption = lastAlpha + + if (lastAlpha > 0f) { + offsetBeforeInterruption = lastOffset + scaleBeforeInterruption = lastScale + alphaBeforeInterruption = lastAlpha + } else { + // Consider the element as not placed in this scene if it was fully transparent. + // TODO(b/290930950): Look into using derived state inside place() instead to not even place + // the element at all when alpha == 0f. + offsetBeforeInterruption = Offset.Unspecified + scaleBeforeInterruption = Scale.Unspecified + alphaBeforeInterruption = Element.AlphaUnspecified + } } private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 6001f1fd6db0..572b1d7ab670 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -138,11 +138,26 @@ internal class MultiPointerDraggableNode( } } + private var _toFloat = orientation.toFunctionOffsetToFloat() + + private fun Offset.toFloat(): Float = _toFloat(this) + + private fun Orientation.toFunctionOffsetToFloat(): (Offset) -> Float = + when (this) { + Orientation.Vertical -> { + { it.y } + } + Orientation.Horizontal -> { + { it.x } + } + } + var orientation: Orientation = orientation set(value) { // Reset the pointer input whenever orientation changed. if (value != field) { field = value + _toFloat = field.toFunctionOffsetToFloat() delegate.resetPointerInputHandler() } } @@ -367,13 +382,6 @@ internal class MultiPointerDraggableNode( return event } - private fun Offset.toFloat(): Float { - return when (orientation) { - Orientation.Vertical -> y - Orientation.Horizontal -> x - } - } - /** * Continues to read drag events until all pointers are up or the drag event is canceled. The * initial pointer to use for driving the drag is [initialPointerId]. [hasDragged] passes the diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index b9251307f1d2..7c8fce8f297d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -459,9 +459,16 @@ data class UserActionResult( /** The key of the transition that should be used. */ val transitionKey: TransitionKey? = null, + + /** + * If `true`, the swipe will be committed and we will settle to [toScene] if only if the user + * swiped at least the swipe distance, i.e. the transition progress was already equal to or + * bigger than 100% when the user released their finger. ` + */ + val requiresFullDistanceSwipe: Boolean = false, ) -interface UserActionDistance { +fun interface UserActionDistance { /** * Return the **absolute** distance of the user action given the size of the scene we are * animating from and the [orientation]. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index a8df6f4ae8b1..5b4fbf036083 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -224,6 +224,9 @@ sealed interface TransitionState { /** The scene this transition is going to. Can't be the same as fromScene */ val toScene: SceneKey, + + /** The transition that `this` transition is replacing, if any. */ + internal val replacedTransition: Transition? = null, ) : TransitionState { /** * The key of this transition. This should usually be null, but it can be specified to use a @@ -279,6 +282,11 @@ sealed interface TransitionState { init { check(fromScene != toScene) + check( + replacedTransition == null || + (replacedTransition.fromScene == fromScene && + replacedTransition.toScene == toScene) + ) } /** @@ -321,6 +329,10 @@ sealed interface TransitionState { return 0f } + if (replacedTransition != null) { + return replacedTransition.interruptionProgress(layoutImpl) + } + fun create(): Animatable<Float, AnimationVector1D> { val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold) layoutImpl.coroutineScope.launch { @@ -521,6 +533,10 @@ internal abstract class BaseSceneTransitionLayoutState( check(transitionStates.size == 1) check(transitionStates[0] is TransitionState.Idle) transitionStates = listOf(transition) + } else if (currentState == transition.replacedTransition) { + // Replace the transition. + transitionStates = + transitionStates.subList(0, transitionStates.lastIndex) + transition } else { // Append the new transition. transitionStates = transitionStates + transition diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/SizeMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/SizeMatcher.kt new file mode 100644 index 000000000000..a4bd2be45297 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/SizeMatcher.kt @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.compose.animation.scene.modifiers + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateMeasurement +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.constrain + +/** + * An object to make one or more destination node be the same size as a source node. + * + * Important: Most of the time, you should not use this class and instead use `Box` together with + * `Modifier.matchParentSize()`. Use this only if you need to use both `Modifier.element()` and + * `Modifier.matchParentSize()` (see b/347910697 for details). + * + * Example: + * ``` + * Box { + * val sizeMatcher = remember { SizeMatcher() } + * + * // The content. + * Content(Modifier.sizeMatcherSource(sizeMatcher)) + * + * // The background. Note that this has to be composed after Content() so that it + * // is measured after it. We set its zIndex to -1 so that it is still placed + * // (drawn) before/below it. We don't use BoxScope.matchParentSize() because it + * // does not play well with Modifier.element(). + * Box( + * Modifier.zIndex(-1f) + * .element(Background) + * // Set the preferred size of this element. + * // Important: This must be *after* the Modifier.element() so that + * // Modifier.element() can override the size. + * .sizeMatcherDestination(sizeMatcher) + * .background( + * MaterialTheme.colorScheme.primaryContainer, + * RoundedCornerShape(32.dp), + * ) + * ) + * } + * ``` + * + * @see sizeMatcherSource + * @see sizeMatcherDestination + */ +class SizeMatcher { + internal var source: LayoutModifierNode? = null + set(value) { + if (value != null && field != null && value != field) { + error("Exactly one Modifier.sizeMatcherSource() should be specified") + } + } + + internal var destinations = mutableSetOf<LayoutModifierNode>() + internal var sourceSize: IntSize = InvalidSize + get() { + if (field == InvalidSize) { + error( + "SizeMatcher size was retrieved before it was set. You should make sure that " + + "all matcher destination are measured *after* the matcher source." + ) + } + return field + } + set(value) { + if (value != field) { + field = value + destinations.forEach { it.invalidateMeasurement() } + } + } + + companion object { + private val InvalidSize = IntSize(Int.MIN_VALUE, Int.MIN_VALUE) + } +} + +/** + * Mark this node as the source of a [SizeMatcher]. + * + * Important: There must be only a single source node associated to a [SizeMatcher] and it must be + * measured before any destination. + */ +fun Modifier.sizeMatcherSource(matcher: SizeMatcher): Modifier { + return this.then(SizeMatcherSourceNodeElement(matcher)) +} + +/** + * Mark this node as the destination of a [SizeMatcher] so that its *preferred* size is the same + * size as the source size. + * + * Important: Destination nodes must be measured *after* the source node, otherwise it might cause + * crashes or 1-frame flickers. For most simple layouts (like Box, Row or Column), this usually + * means that the destinations nodes must be composed *after* the source node. If doing so is + * causing layering issues, you can use `Modifier.zIndex` to explicitly set the placement order of + * your composables. + */ +fun Modifier.sizeMatcherDestination(matcher: SizeMatcher): Modifier { + return this.then(SizeMatcherDestinationElement(matcher)) +} + +private data class SizeMatcherSourceNodeElement( + private val matcher: SizeMatcher, +) : ModifierNodeElement<SizeMatcherSourceNode>() { + override fun create(): SizeMatcherSourceNode = SizeMatcherSourceNode(matcher) + + override fun update(node: SizeMatcherSourceNode) { + node.update(matcher) + } +} + +private class SizeMatcherSourceNode( + private var matcher: SizeMatcher, +) : Modifier.Node(), LayoutModifierNode { + override fun onAttach() { + matcher.source = this + } + + override fun onDetach() { + matcher.source = null + } + + fun update(matcher: SizeMatcher) { + val previous = this.matcher + this.matcher = matcher + + previous.source = null + matcher.source = this + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + return measurable.measure(constraints).run { + matcher.sourceSize = IntSize(width, height) + layout(width, height) { place(0, 0) } + } + } +} + +private data class SizeMatcherDestinationElement( + private val matcher: SizeMatcher, +) : ModifierNodeElement<SizeMatcherDestinationNode>() { + override fun create(): SizeMatcherDestinationNode = SizeMatcherDestinationNode(matcher) + + override fun update(node: SizeMatcherDestinationNode) { + node.update(matcher) + } +} + +private class SizeMatcherDestinationNode( + private var matcher: SizeMatcher, +) : Modifier.Node(), LayoutModifierNode { + override fun onAttach() { + this.matcher.destinations.add(this) + } + + override fun onDetach() { + this.matcher.destinations.remove(this) + } + + fun update(matcher: SizeMatcher) { + val previous = this.matcher + this.matcher = matcher + + previous.destinations.remove(this) + matcher.destinations.add(this) + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + val preferredSize = matcher.sourceSize + val preferredConstraints = Constraints.fixed(preferredSize.width, preferredSize.height) + + // Make sure we still respect the incoming constraints. + val placeable = measurable.measure(constraints.constrain(preferredConstraints)) + return layout(placeable.width, placeable.height) { placeable.place(0, 0) } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index f532e2ed245a..65b388f66a0d 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -1195,4 +1195,54 @@ class DraggableHandlerTest { assertThat(transition).hasProgress(0f) assertThat(transition).hasOverscrollSpec() } + + @Test + fun interceptingTransitionKeepsDistance() = runGestureTest { + var swipeDistance = 75f + layoutState.transitions = transitions { + from(SceneA, to = SceneB) { distance = UserActionDistance { _, _ -> swipeDistance } } + } + + // Start transition. + val controller = onDragStarted(overSlop = -50f) + assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f) + + // Intercept the transition and change the swipe distance. The original distance and + // progress should be the same. + swipeDistance = 50f + controller.onDragStopped(0f) + onDragStartedImmediately() + assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f) + } + + @Test + fun requireFullDistanceSwipe() = runGestureTest { + mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB, requiresFullDistanceSwipe = true) + + val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.9f)) + assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.9f) + + controller.onDragStopped(velocity = 0f) + advanceUntilIdle() + assertIdle(SceneA) + + val otherController = onDragStarted(overSlop = up(fractionOfScreen = 1f)) + assertTransition(fromScene = SceneA, toScene = SceneB, progress = 1f) + otherController.onDragStopped(velocity = 0f) + advanceUntilIdle() + assertIdle(SceneB) + } + + @Test + fun interceptingTransitionReplacesCurrentTransition() = runGestureTest { + val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.5f)) + val transition = assertThat(layoutState.transitionState).isTransition() + controller.onDragStopped(velocity = 0f) + + // Intercept the transition. + onDragStartedImmediately() + val newTransition = assertThat(layoutState.transitionState).isTransition() + assertThat(newTransition).isNotSameInstanceAs(transition) + assertThat(newTransition.replacedTransition).isSameInstanceAs(transition) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index a18da7364983..fcdf76ef8737 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -731,7 +731,7 @@ class ElementTest { onAnimatedFloat = { animatedFloat = it }, ) - val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag) fooElement.assertTopPositionInRootIsEqualTo(0.dp) val transition = assertThat(state.transitionState).isTransition() assertThat(transition).isNotNull() @@ -811,7 +811,7 @@ class ElementTest { } assertThat(state.transitionState).isIdle() - val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag) fooElement.assertTopPositionInRootIsEqualTo(0.dp) // Swipe by half of verticalSwipeDistance. @@ -858,7 +858,7 @@ class ElementTest { onAnimatedFloat = { animatedFloat = it }, ) - val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag) fooElement.assertTopPositionInRootIsEqualTo(0.dp) assertThat(animatedFloat).isEqualTo(100f) @@ -914,7 +914,7 @@ class ElementTest { onAnimatedFloat = { animatedFloat = it }, ) - val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag) fooElement.assertTopPositionInRootIsEqualTo(0.dp) assertThat(animatedFloat).isEqualTo(100f) @@ -1937,4 +1937,119 @@ class ElementTest { ) .isEqualTo(Element.SizeUnspecified) } + + @Test + fun transparentElementIsNotImpactingInterruption() = runTest { + val state = + rule.runOnIdle { + MutableSceneTransitionLayoutStateImpl( + SceneA, + transitions { + from(SceneA, to = SceneB) { + // In A => B, Foo is not shared and first fades out from A then fades in + // B. + sharedElement(TestElements.Foo, enabled = false) + fractionRange(end = 0.5f) { fade(TestElements.Foo.inScene(SceneA)) } + fractionRange(start = 0.5f) { fade(TestElements.Foo.inScene(SceneB)) } + } + + from(SceneB, to = SceneA) { + // In B => A, Foo is shared. + sharedElement(TestElements.Foo, enabled = true) + } + } + ) + } + + @Composable + fun SceneScope.Foo(modifier: Modifier = Modifier) { + Box(modifier.element(TestElements.Foo).size(10.dp)) + } + + rule.setContent { + SceneTransitionLayout(state) { + scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) } + + // Define A after B so that Foo is placed in A during A <=> B. + scene(SceneA) { Foo() } + } + } + + // Start A => B at 70%. + rule.runOnUiThread { + state.startTransition( + transition( + from = SceneA, + to = SceneB, + progress = { 0.7f }, + onFinish = neverFinish(), + ) + ) + } + + rule.onNode(isElement(TestElements.Foo, SceneA)).assertPositionInRootIsEqualTo(0.dp, 0.dp) + rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(40.dp, 60.dp) + + // Start B => A at 50% with interruptionProgress = 100%. Foo is placed in A and should still + // be at (40dp, 60dp) given that it was fully transparent in A before the interruption. + var interruptionProgress by mutableStateOf(1f) + rule.runOnUiThread { + state.startTransition( + transition( + from = SceneB, + to = SceneA, + progress = { 0.5f }, + interruptionProgress = { interruptionProgress }, + onFinish = neverFinish(), + ) + ) + } + + rule.onNode(isElement(TestElements.Foo, SceneA)).assertPositionInRootIsEqualTo(40.dp, 60.dp) + rule.onNode(isElement(TestElements.Foo, SceneB)).assertIsNotDisplayed() + + // Set the interruption progress to 0%. Foo should be at (20dp, 30dp) given that B => is at + // 50%. + interruptionProgress = 0f + rule.onNode(isElement(TestElements.Foo, SceneA)).assertPositionInRootIsEqualTo(20.dp, 30.dp) + rule.onNode(isElement(TestElements.Foo, SceneB)).assertIsNotDisplayed() + } + + @Test + fun replacedTransitionDoesNotTriggerInterruption() = runTest { + val state = rule.runOnIdle { MutableSceneTransitionLayoutStateImpl(SceneA) } + + @Composable + fun SceneScope.Foo(modifier: Modifier = Modifier) { + Box(modifier.element(TestElements.Foo).size(10.dp)) + } + + rule.setContent { + SceneTransitionLayout(state) { + scene(SceneA) { Foo() } + scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) } + } + } + + // Start A => B at 50%. + val aToB1 = + transition(from = SceneA, to = SceneB, progress = { 0.5f }, onFinish = neverFinish()) + rule.runOnUiThread { state.startTransition(aToB1) } + rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed() + rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(20.dp, 30.dp) + + // Replace A => B by another A => B at 100%. Even with interruption progress at 100%, Foo + // should be at (40dp, 60dp) given that aToB1 was replaced by aToB2. + val aToB2 = + transition( + from = SceneA, + to = SceneB, + progress = { 1f }, + interruptionProgress = { 1f }, + replacedTransition = aToB1, + ) + rule.runOnUiThread { state.startTransition(aToB2) } + rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed() + rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(40.dp, 60.dp) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt index 09d1a827d0c7..3552d3d0bc1b 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt @@ -131,10 +131,6 @@ class InterruptionHandlerTest { assertThat(state.currentTransitions) .comparingElementsUsing(FromToCurrentTriple) .containsExactly( - // Initial transition A to B. This transition will never be consumed by anyone given - // that it has the same (from, to) pair as the next transition. - Triple(SceneA, SceneB, SceneB), - // Initial transition reversed, B back to A. Triple(SceneA, SceneB, SceneA), diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt index 322b0355c2bd..65f4f9e6e393 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt @@ -37,8 +37,11 @@ fun transition( bouncingScene: SceneKey? = null, orientation: Orientation = Orientation.Horizontal, onFinish: ((TransitionState.Transition) -> Job)? = null, + replacedTransition: TransitionState.Transition? = null, ): TransitionState.Transition { - return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties { + return object : + TransitionState.Transition(from, to, replacedTransition), + TransitionState.HasOverscrollProperties { override val currentScene: SceneKey get() = current() diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeMatcherTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeMatcherTest.kt new file mode 100644 index 000000000000..fa2496686170 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeMatcherTest.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.compose.animation.scene.modifiers + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.test.assertSizeIsEqualTo +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SizeMatcherTest { + @get:Rule val rule = createComposeRule() + + @Test + fun sizeMatcher() { + val contentSize = DpSize(200.dp, 100.dp) + val sizeMatcher = SizeMatcher() + val backgroundTag = "background" + + rule.setContent { + Box { + Box(Modifier.sizeMatcherSource(sizeMatcher).size(contentSize)) + Box(Modifier.testTag(backgroundTag).sizeMatcherDestination(sizeMatcher)) + } + } + + rule.onNodeWithTag(backgroundTag).assertSizeIsEqualTo(contentSize.width, contentSize.height) + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt index e39d7edddcfd..9e857deb280a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt @@ -21,16 +21,16 @@ import com.android.systemui.shared.settings.data.repository.SecureSettingsReposi import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext /** Provides access to state related to notification settings. */ class NotificationSettingsRepository( - scope: CoroutineScope, + private val scope: CoroutineScope, private val backgroundDispatcher: CoroutineDispatcher, private val secureSettingsRepository: SecureSettingsRepository, ) { @@ -41,16 +41,15 @@ class NotificationSettingsRepository( .distinctUntilChanged() /** The current state of the notification setting. */ - val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> = + suspend fun isShowNotificationsOnLockScreenEnabled(): StateFlow<Boolean> = secureSettingsRepository .intSetting( name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, ) .map { it == 1 } + .flowOn(backgroundDispatcher) .stateIn( scope = scope, - started = SharingStarted.WhileSubscribed(), - initialValue = false, ) suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt index 04e8090e3ae2..b4105bdecc0a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt @@ -26,8 +26,8 @@ class NotificationSettingsInteractor( val isNotificationHistoryEnabled = repository.isNotificationHistoryEnabled /** Should notifications be visible on the lockscreen? */ - val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> = - repository.isShowNotificationsOnLockScreenEnabled + suspend fun isShowNotificationsOnLockScreenEnabled(): StateFlow<Boolean> = + repository.isShowNotificationsOnLockScreenEnabled() suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) { repository.setShowNotificationsOnLockscreenEnabled(enabled) @@ -35,7 +35,7 @@ class NotificationSettingsInteractor( /** Toggles the setting to show or hide notifications on the lock screen. */ suspend fun toggleShowNotificationsOnLockscreenEnabled() { - val current = repository.isShowNotificationsOnLockScreenEnabled.value + val current = repository.isShowNotificationsOnLockScreenEnabled().value repository.setShowNotificationsOnLockscreenEnabled(!current) } } diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml index 4def93fcdf91..2fd7f1b53fba 100644 --- a/packages/SystemUI/lint-baseline.xml +++ b/packages/SystemUI/lint-baseline.xml @@ -27088,17 +27088,6 @@ <issue id="UselessParent" - message="This `FrameLayout` layout or its `LinearLayout` parent is unnecessary" - errorLine1=" <FrameLayout" - errorLine2=" ~~~~~~~~~~~"> - <location - file="frameworks/base/packages/SystemUI/res/layout/media_output_list_item.xml" - line="24" - column="6"/> - </issue> - - <issue - id="UselessParent" message="This `LinearLayout` layout or its `FrameLayout` parent is possibly unnecessary; transfer the `background` attribute to the other view" errorLine1=" <LinearLayout" errorLine2=" ~~~~~~~~~~~~"> @@ -30587,17 +30576,6 @@ <issue id="ContentDescription" message="Missing `contentDescription` attribute on image" - errorLine1=" <ImageView" - errorLine2=" ~~~~~~~~~"> - <location - file="frameworks/base/packages/SystemUI/res/layout/media_output_list_item.xml" - line="54" - column="14"/> - </issue> - - <issue - id="ContentDescription" - message="Missing `contentDescription` attribute on image" errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> <location diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt index 303548131788..68cfa28dabd7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.biometricSettingsRepository @@ -223,23 +222,14 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } private fun givenAlternateBouncerSupported() { - if (DeviceEntryUdfpsRefactor.isEnabled) { - kosmos.fingerprintPropertyRepository.supportsUdfps() - } else { - kosmos.keyguardBouncerRepository.setAlternateBouncerUIAvailable(true) - } + kosmos.givenAlternateBouncerSupported() } private fun givenCanShowAlternateBouncer() { - givenAlternateBouncerSupported() - kosmos.keyguardBouncerRepository.setPrimaryShow(false) - kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) - kosmos.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) - whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) - whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false) + kosmos.givenCanShowAlternateBouncer() } private fun givenCannotShowAlternateBouncer() { - kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + kosmos.givenCannotShowAlternateBouncer() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index a5acf724dcff..ccddc9c7120f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -38,7 +38,7 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope -import com.android.systemui.scene.domain.interactor.sceneContainerStartable +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index cf145471e55f..fbe2c2e97a9c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -451,6 +451,24 @@ class CommunalSceneStartableTest : SysuiTestCase() { } } + @Test + fun transitionFromDozingToGlanceableHub_forcesCommunal() = + with(kosmos) { + testScope.runTest { + val scene by collectLastValue(communalSceneInteractor.currentScene) + communalSceneInteractor.changeScene(CommunalScenes.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) + + fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DOZING, + to = KeyguardState.GLANCEABLE_HUB, + testScope = this + ) + + assertThat(scene).isEqualTo(CommunalScenes.Communal) + } + } + private fun TestScope.updateDocked(docked: Boolean) = with(kosmos) { runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt index 5e120b5f9560..a8bdc7c632d2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.communal.data.repository import android.content.Context import android.content.Intent -import android.content.SharedPreferences import android.content.pm.UserInfo +import android.content.pm.UserInfo.FLAG_MAIN import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -30,108 +30,87 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer -import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.fakeUserFileManager import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.user.data.repository.fakeUserRepository -import com.android.systemui.util.FakeSharedPreferences import com.google.common.truth.Truth.assertThat -import java.io.File import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.spy @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class CommunalPrefsRepositoryImplTest : SysuiTestCase() { - @Mock private lateinit var tableLogBuffer: TableLogBuffer - - private lateinit var underTest: CommunalPrefsRepositoryImpl - private val kosmos = testKosmos() private val testScope = kosmos.testScope - private lateinit var userRepository: FakeUserRepository - private lateinit var userFileManager: UserFileManager - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) + private val userFileManager: UserFileManager = spy(kosmos.fakeUserFileManager) - userRepository = kosmos.fakeUserRepository - userRepository.setUserInfos(USER_INFOS) - - userFileManager = - FakeUserFileManager( - mapOf( - USER_INFOS[0].id to FakeSharedPreferences(), - USER_INFOS[1].id to FakeSharedPreferences() - ) - ) + private val underTest: CommunalPrefsRepositoryImpl by lazy { + CommunalPrefsRepositoryImpl( + kosmos.testDispatcher, + userFileManager, + kosmos.broadcastDispatcher, + logcatLogBuffer("CommunalPrefsRepositoryImplTest"), + ) } @Test fun isCtaDismissedValue_byDefault_isFalse() = testScope.runTest { - underTest = createCommunalPrefsRepositoryImpl(userFileManager) - val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER)) assertThat(isCtaDismissed).isFalse() } @Test fun isCtaDismissedValue_onSet_isTrue() = testScope.runTest { - underTest = createCommunalPrefsRepositoryImpl(userFileManager) - val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER)) - underTest.setCtaDismissedForCurrentUser() + underTest.setCtaDismissed(MAIN_USER) assertThat(isCtaDismissed).isTrue() } @Test - fun isCtaDismissedValue_whenSwitchUser() = + fun isCtaDismissedValue_onSetForDifferentUser_isStillFalse() = testScope.runTest { - underTest = createCommunalPrefsRepositoryImpl(userFileManager) - val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) - underTest.setCtaDismissedForCurrentUser() - - // dismissed true for primary user - assertThat(isCtaDismissed).isTrue() - - // switch to secondary user - userRepository.setSelectedUserInfo(USER_INFOS[1]) + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER)) - // dismissed is false for secondary user + underTest.setCtaDismissed(SECONDARY_USER) assertThat(isCtaDismissed).isFalse() + } + + @Test + fun isDisclaimerDismissed_byDefault_isFalse() = + testScope.runTest { + val isDisclaimerDismissed by + collectLastValue(underTest.isDisclaimerDismissed(MAIN_USER)) + assertThat(isDisclaimerDismissed).isFalse() + } - // switch back to primary user - userRepository.setSelectedUserInfo(USER_INFOS[0]) + @Test + fun isDisclaimerDismissed_onSet_isTrue() = + testScope.runTest { + val isDisclaimerDismissed by + collectLastValue(underTest.isDisclaimerDismissed(MAIN_USER)) - // dismissed is true for primary user - assertThat(isCtaDismissed).isTrue() + underTest.setDisclaimerDismissed(MAIN_USER) + assertThat(isDisclaimerDismissed).isTrue() } @Test fun getSharedPreferences_whenFileRestored() = testScope.runTest { - val userFileManagerSpy = Mockito.spy(userFileManager) - underTest = createCommunalPrefsRepositoryImpl(userFileManagerSpy) - - val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) - userRepository.setSelectedUserInfo(USER_INFOS[0]) + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER)) assertThat(isCtaDismissed).isFalse() - clearInvocations(userFileManagerSpy) + clearInvocations(userFileManager) // Received restore finished event. kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly( @@ -141,48 +120,12 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() { runCurrent() // Get shared preferences from the restored file. - verify(userFileManagerSpy, atLeastOnce()) - .getSharedPreferences( - FILE_NAME, - Context.MODE_PRIVATE, - userRepository.getSelectedUserInfo().id - ) - } - - private fun createCommunalPrefsRepositoryImpl(userFileManager: UserFileManager) = - CommunalPrefsRepositoryImpl( - testScope.backgroundScope, - kosmos.testDispatcher, - userRepository, - userFileManager, - kosmos.broadcastDispatcher, - logcatLogBuffer("CommunalPrefsRepositoryImplTest"), - tableLogBuffer, - ) - - private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) : - UserFileManager { - override fun getFile(fileName: String, userId: Int): File { - throw UnsupportedOperationException() + verify(userFileManager, atLeastOnce()) + .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, MAIN_USER.id) } - override fun getSharedPreferences( - fileName: String, - mode: Int, - userId: Int - ): SharedPreferences { - if (fileName != FILE_NAME) { - throw IllegalArgumentException("Preference files must be $FILE_NAME") - } - return sharedPrefs.getValue(userId) - } - } - companion object { - val USER_INFOS = - listOf( - UserInfo(/* id= */ 0, "zero", /* flags= */ 0), - UserInfo(/* id= */ 1, "secondary", /* flags= */ 0), - ) + val MAIN_USER = UserInfo(0, "main", FLAG_MAIN) + val SECONDARY_USER = UserInfo(1, "secondary", 0) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 3d454a202220..d951cca89f64 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -488,8 +488,16 @@ class CommunalInteractorTest : SysuiTestCase() { @Test fun ctaTile_afterDismiss_doesNotShow() = testScope.runTest { + // Set to main user, so we can dismiss the tile for the main user. + val user = userRepository.asMainUser() + userTracker.set( + userInfos = listOf(user), + selectedUserIndex = 0, + ) + runCurrent() + tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalPrefsRepository.setCtaDismissedForCurrentUser() + communalPrefsRepository.setCtaDismissed(user) val ctaTileContent by collectLastValue(underTest.ctaTileContent) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt new file mode 100644 index 000000000000..7b79d2817478 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.communal.domain.interactor + +import android.content.pm.UserInfo +import android.content.pm.UserInfo.FLAG_MAIN +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalPrefsInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val underTest by lazy { kosmos.communalPrefsInteractor } + + @Test + fun setCtaDismissed_currentUser() = + testScope.runTest { + setSelectedUser(MAIN_USER) + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + + assertThat(isCtaDismissed).isFalse() + underTest.setCtaDismissed(MAIN_USER) + assertThat(isCtaDismissed).isTrue() + } + + @Test + fun setCtaDismissed_anotherUser() = + testScope.runTest { + setSelectedUser(MAIN_USER) + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + + assertThat(isCtaDismissed).isFalse() + underTest.setCtaDismissed(SECONDARY_USER) + assertThat(isCtaDismissed).isFalse() + } + + @Test + fun isCtaDismissed_userSwitch() = + testScope.runTest { + setSelectedUser(MAIN_USER) + underTest.setCtaDismissed(MAIN_USER) + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + + assertThat(isCtaDismissed).isTrue() + setSelectedUser(SECONDARY_USER) + assertThat(isCtaDismissed).isFalse() + } + + @Test + fun setDisclaimerDismissed_currentUser() = + testScope.runTest { + setSelectedUser(MAIN_USER) + val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed) + + assertThat(isDisclaimerDismissed).isFalse() + underTest.setDisclaimerDismissed(MAIN_USER) + assertThat(isDisclaimerDismissed).isTrue() + } + + @Test + fun setDisclaimerDismissed_anotherUser() = + testScope.runTest { + setSelectedUser(MAIN_USER) + val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed) + + assertThat(isDisclaimerDismissed).isFalse() + underTest.setDisclaimerDismissed(SECONDARY_USER) + assertThat(isDisclaimerDismissed).isFalse() + } + + @Test + fun isDisclaimerDismissed_userSwitch() = + testScope.runTest { + setSelectedUser(MAIN_USER) + underTest.setDisclaimerDismissed(MAIN_USER) + val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed) + + assertThat(isDisclaimerDismissed).isTrue() + setSelectedUser(SECONDARY_USER) + assertThat(isDisclaimerDismissed).isFalse() + } + + private suspend fun setSelectedUser(user: UserInfo) { + with(kosmos.fakeUserRepository) { + setUserInfos(listOf(user)) + setSelectedUserInfo(user) + } + kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0) + } + + private companion object { + val MAIN_USER = UserInfo(0, "main", FLAG_MAIN) + val SECONDARY_USER = UserInfo(1, "secondary", 0) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt index 6e48b999af47..43293c7a50ca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt @@ -83,24 +83,27 @@ class CommunalSceneInteractorTest : SysuiTestCase() { } @Test - fun snapToSceneForActivity() = + fun changeSceneForActivityStartOnDismissKeyguard() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(CommunalScenes.Blank) - - underTest.snapToSceneForActivityStart(CommunalScenes.Communal) + underTest.snapToScene(CommunalScenes.Communal) assertThat(currentScene).isEqualTo(CommunalScenes.Communal) + + underTest.changeSceneForActivityStartOnDismissKeyguard() + assertThat(currentScene).isEqualTo(CommunalScenes.Blank) } @Test - fun snapToSceneForActivity_willNotChangeScene_forEditModeActivity() = + fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(CommunalScenes.Blank) + underTest.snapToScene(CommunalScenes.Communal) + assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.setEditModeState(EditModeState.STARTING) - underTest.snapToSceneForActivityStart(CommunalScenes.Communal) - assertThat(currentScene).isEqualTo(CommunalScenes.Blank) + + underTest.changeSceneForActivityStartOnDismissKeyguard() + assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index d5fe2a1b2e47..0190ccba3caa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalPrefsInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.model.CommunalContentModel @@ -48,6 +49,8 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -57,6 +60,7 @@ import com.android.systemui.settings.fakeUserTracker import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -104,10 +108,12 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { smartspaceRepository = kosmos.fakeSmartspaceRepository mediaRepository = kosmos.fakeCommunalMediaRepository communalSceneInteractor = kosmos.communalSceneInteractor + kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO)) kosmos.fakeUserTracker.set( userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0, ) + kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id)) underTest = @@ -120,6 +126,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { uiEventLogger, logcatLogBuffer("CommunalEditModeViewModelTest"), kosmos.testDispatcher, + kosmos.communalPrefsInteractor, ) } @@ -312,6 +319,29 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { } } + @Test + fun showDisclaimer_trueAfterEditModeShowing() = + testScope.runTest { + val showDisclaimer by collectLastValue(underTest.showDisclaimer) + + assertThat(showDisclaimer).isFalse() + underTest.setEditModeState(EditModeState.SHOWING) + assertThat(showDisclaimer).isTrue() + } + + @Test + fun showDisclaimer_falseWhenDismissed() = + testScope.runTest { + underTest.setEditModeState(EditModeState.SHOWING) + kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) + + val showDisclaimer by collectLastValue(underTest.showDisclaimer) + + assertThat(showDisclaimer).isTrue() + underTest.onDisclaimerDismissed() + assertThat(showDisclaimer).isFalse() + } + private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index e7a7b152d936..7a5f81c1ed29 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -100,7 +100,6 @@ import platform.test.runner.parameterized.Parameters @RunWith(ParameterizedAndroidJunit4::class) class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost - @Mock private lateinit var user: UserInfo @Mock private lateinit var providerInfo: AppWidgetProviderInfo private val kosmos = testKosmos() @@ -315,6 +314,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() = testScope.runTest { + setIsMainUser(true) tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) val communalContent by collectLastValue(underTest.communalContent) @@ -338,6 +338,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun popup_onDismiss_hidesImmediately() = testScope.runTest { + setIsMainUser(true) tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) val currentPopup by collectLastValue(underTest.currentPopup) @@ -743,13 +744,17 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { } private suspend fun setIsMainUser(isMainUser: Boolean) { - whenever(user.isMain).thenReturn(isMainUser) - userRepository.setUserInfos(listOf(user)) - userRepository.setSelectedUserInfo(user) + val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO + with(userRepository) { + setUserInfos(listOf(user)) + setSelectedUserInfo(user) + } + kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0) } private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) + val SECONDARY_USER_INFO = UserInfo(1, "secondary", 0) @JvmStatic @Parameters(name = "{0}") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryTest.kt new file mode 100644 index 000000000000..79115ae2ebdd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.deviceconfig.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.fakeDeviceConfigProxy +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceConfigRepositoryTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val dataSource = kosmos.fakeDeviceConfigProxy + + private val underTest = kosmos.deviceConfigRepository + + @Test + fun booleanProperty() = + testScope.runTest { + val property by collectLastValue(underTest.property("namespace", "name", false)) + assertThat(property).isFalse() + + dataSource.setProperty("namespace", "name", "true", /* makeDefault= */ false) + kosmos.fakeExecutor.runAllReady() + assertThat(property).isTrue() + + dataSource.setProperty("namespace", "name", "false", /* makeDefault= */ false) + kosmos.fakeExecutor.runAllReady() + assertThat(property).isFalse() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorTest.kt new file mode 100644 index 000000000000..6ec4ceac2ab2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.deviceconfig.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.fakeDeviceConfigProxy +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceConfigInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val dataSource = kosmos.fakeDeviceConfigProxy + + private val underTest = kosmos.deviceConfigInteractor + + @Test + fun booleanProperty() = + testScope.runTest { + val property by collectLastValue(underTest.property("namespace", "name", false)) + assertThat(property).isFalse() + + dataSource.setProperty("namespace", "name", "true", /* makeDefault= */ false) + kosmos.fakeExecutor.runAllReady() + assertThat(property).isTrue() + + dataSource.setProperty("namespace", "name", "false", /* makeDefault= */ false) + kosmos.fakeExecutor.runAllReady() + assertThat(property).isFalse() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index ee8a22c17455..5a39de8392be 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -778,6 +778,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { DREAM_COMPONENT, false /*shouldShowComplication*/ ) + testScope.runCurrent() mMainExecutor.runAllReady() assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt index 3d3c7788dd56..74eee9b24cbc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt @@ -144,7 +144,7 @@ class QSLongPressEffectTest : SysuiTestCase() { longPressEffect.handleActionUp() // THEN the effect reverses - assertEffectReverses() + assertEffectReverses(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) } @Test @@ -171,18 +171,17 @@ class QSLongPressEffectTest : SysuiTestCase() { longPressEffect.handleActionCancel() // THEN the effect gets reversed - assertEffectReverses() + assertEffectReverses(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) } @Test - fun onAnimationComplete_keyguardDismissible_effectEndsWithPrepare() = + fun onAnimationComplete_keyguardDismissible_effectCompletes() = testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) { // GIVEN that the animation completes longPressEffect.handleAnimationComplete() - // THEN the long-press effect completes and the view is called to prepare + // THEN the long-press effect completes assertEffectCompleted() - verify(callback, times(1)).onPrepareForLaunch() } @Test @@ -200,6 +199,26 @@ class QSLongPressEffectTest : SysuiTestCase() { } @Test + fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversing() = + testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) { + // GIVEN that the animation completes + longPressEffect.handleAnimationComplete() + + // THEN the callback for finished reversing is used. + verify(callback, times(1)).onEffectFinishedReversing() + } + + @Test + fun onAnimationComplete_whenRunningBackwardsFromCancel_endsInIdle() = + testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) { + // GIVEN that the animation completes + longPressEffect.handleAnimationComplete() + + // THEN the effect ends in the idle state. + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) + } + + @Test fun onActionDown_whileRunningBackwards_cancels() = testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) { // GIVEN an action cancel occurs and the effect gets reversed @@ -223,11 +242,8 @@ class QSLongPressEffectTest : SysuiTestCase() { } @Test - fun onAnimationComplete_whileRunningBackwards_goesToIdle() = - testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS) { - // GIVEN an action cancel occurs and the effect gets reversed - longPressEffect.handleActionCancel() - + fun onAnimationComplete_whileRunningBackwardsFromCancel_goesToIdle() = + testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) { // GIVEN that the animation completes longPressEffect.handleAnimationComplete() @@ -307,12 +323,16 @@ class QSLongPressEffectTest : SysuiTestCase() { /** * Asserts that the effect did not start by checking that: * 1. No haptics are played - * 2. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or - * [QSLongPressEffect.State.RUNNING_FORWARD] + * 2. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP] or + * [QSLongPressEffect.State.RUNNING_FORWARD] or + * [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL] */ private fun assertEffectDidNotStart() { assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD) - assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS) + assertThat(longPressEffect.state) + .isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) + assertThat(longPressEffect.state) + .isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @@ -330,12 +350,14 @@ class QSLongPressEffectTest : SysuiTestCase() { } /** - * Assert that the effect gets reverted by checking that: - * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS] - * 2. An action to reverse the animator is emitted + * Assert that the effect gets reverted by checking that the callback to reverse the animator is + * used, and that the state is given reversing state. + * + * @param[reversingState] Either [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL] or + * [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP] */ - private fun assertEffectReverses() { - assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS) + private fun assertEffectReverses(reversingState: QSLongPressEffect.State) { + assertThat(longPressEffect.state).isEqualTo(reversingState) verify(callback, times(1)).onReverseAnimator() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 4587ea6dbdc8..c5ba02d0773a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -528,6 +528,30 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { } @Test + fun userChange_isFingerprintEnrolledAndEnabledUpdated() = + testScope.runTest { + createBiometricSettingsRepository() + whenever(authController.isFingerprintEnrolled(ANOTHER_USER_ID)).thenReturn(false) + whenever(authController.isFingerprintEnrolled(PRIMARY_USER_ID)).thenReturn(true) + + verify(biometricManager) + .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture()) + val isFingerprintEnrolledAndEnabled = + collectLastValue(underTest.isFingerprintEnrolledAndEnabled) + biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID) + runCurrent() + userRepository.setSelectedUserInfo(ANOTHER_USER) + runCurrent() + assertThat(isFingerprintEnrolledAndEnabled()).isFalse() + + biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID) + runCurrent() + userRepository.setSelectedUserInfo(PRIMARY_USER) + runCurrent() + assertThat(isFingerprintEnrolledAndEnabled()).isTrue() + } + + @Test fun userChange_biometricEnabledChange_handlesRaceCondition() = testScope.runTest { createBiometricSettingsRepository() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 5dac37a0c0f0..48caf3ed2469 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -140,6 +140,27 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun panelAlpha() = + testScope.runTest { + assertThat(underTest.panelAlpha.value).isEqualTo(1f) + + underTest.setPanelAlpha(0.1f) + assertThat(underTest.panelAlpha.value).isEqualTo(0.1f) + + underTest.setPanelAlpha(0.2f) + assertThat(underTest.panelAlpha.value).isEqualTo(0.2f) + + underTest.setPanelAlpha(0.3f) + assertThat(underTest.panelAlpha.value).isEqualTo(0.3f) + + underTest.setPanelAlpha(0.5f) + assertThat(underTest.panelAlpha.value).isEqualTo(0.5f) + + underTest.setPanelAlpha(1.0f) + assertThat(underTest.panelAlpha.value).isEqualTo(1f) + } + + @Test fun topClippingBounds() = testScope.runTest { assertThat(underTest.topClippingBounds.value).isNull() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt index 5f0f24dcca86..2d121502650e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -61,7 +62,8 @@ class TrustRepositoryTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() + val testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) userRepository = FakeUserRepository() userRepository.setUserInfos(users) val logger = @@ -69,7 +71,13 @@ class TrustRepositoryTest : SysuiTestCase() { LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false) ) underTest = - TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger) + TrustRepositoryImpl( + testScope.backgroundScope, + testDispatcher, + userRepository, + trustManager, + logger, + ) } fun TestScope.init() { @@ -275,4 +283,11 @@ class TrustRepositoryTest : SysuiTestCase() { userRepository.setSelectedUserInfo(users[1]) assertThat(trustUsuallyManaged).isFalse() } + + @Test + fun reportKeyguardShowingChanged() = + testScope.runTest { + underTest.reportKeyguardShowingChanged() + verify(trustManager).reportKeyguardShowingChanged() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt index d20fec44d60f..5115f5af94f0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt @@ -90,7 +90,11 @@ class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() { ) reset(transitionRepository) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null) kosmos.fakeKeyguardRepository.setKeyguardOccluded(true) + runCurrent() + assertThat(transitionRepository).noTransitionsStarted() + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true) runCurrent() kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index 612f2e73e4bb..0792a50f7108 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -34,12 +34,14 @@ package com.android.systemui.keyguard.domain.interactor import android.os.PowerManager import android.platform.test.annotations.EnableFlags +import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository +import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -64,8 +66,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.reset import org.mockito.Mockito.spy +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -120,6 +124,66 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToLockscreen_onWakeup_canDream_glanceableHubAvailable() = + testScope.runTest { + whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true) + kosmos.setCommunalAvailable(true) + runCurrent() + + powerInteractor.setAwakeForTest() + runCurrent() + + // If dreaming is possible and communal is available, then we should transition to + // GLANCEABLE_HUB when waking up. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DOZING, + to = KeyguardState.GLANCEABLE_HUB, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToLockscreen_onWakeup_canNotDream_glanceableHubAvailable() = + testScope.runTest { + whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false) + kosmos.setCommunalAvailable(true) + runCurrent() + + powerInteractor.setAwakeForTest() + runCurrent() + + // If dreaming is NOT possible but communal is available, then we should transition to + // LOCKSCREEN when waking up. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToLockscreen_onWakeup_canNDream_glanceableHubNotAvailable() = + testScope.runTest { + whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true) + kosmos.setCommunalAvailable(false) + runCurrent() + + powerInteractor.setAwakeForTest() + runCurrent() + + // If dreaming is possible but communal is NOT available, then we should transition to + // LOCKSCREEN when waking up. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() = testScope.runTest { kosmos.fakeCommunalSceneRepository.setTransitionState( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 519bb6eb7834..63d06a409522 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -55,6 +55,8 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Mock private lateinit var burnInInteractor: BurnInInteractor @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel + @Mock + private lateinit var lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController private val kosmos = testKosmos() @@ -76,7 +78,12 @@ class AodBurnInViewModelTest : SysuiTestCase() { kosmos.burnInInteractor = burnInInteractor whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) .thenReturn(emptyFlow()) + whenever(goneToAodTransitionViewModel.enterFromSideTranslationX(anyInt())) + .thenReturn(emptyFlow()) + whenever(lockscreenToAodTransitionViewModel.enterFromSideTranslationX(anyInt())) + .thenReturn(emptyFlow()) kosmos.goneToAodTransitionViewModel = goneToAodTransitionViewModel + kosmos.lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel kosmos.fakeKeyguardClockRepository.setCurrentClock(clockController) underTest = kosmos.aodBurnInViewModel diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt index 6d9c271d0f4a..b49e546c6e78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt @@ -227,6 +227,15 @@ class DeviceEntryIconViewModelTest : SysuiTestCase() { assertThat(accessibilityDelegateHint) .isEqualTo(DeviceEntryIconView.AccessibilityHintType.BOUNCER) + // udfps running + setUpState( + isUdfpsSupported = true, + isUdfpsRunning = true, + ) + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.BOUNCER) + // non-interactive lock icon fingerprintPropertyRepository.supportsRearFps() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt index 716c40d59ccf..bab466a6c1d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt @@ -28,6 +28,9 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.powerRepository +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat @@ -47,10 +50,18 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { private val underTest = kosmos.goneToAodTransitionViewModel private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository private val biometricSettingsRepository = kosmos.biometricSettingsRepository + private val powerRepository = kosmos.powerRepository @Test - fun enterFromTopTranslationY() = + fun enterFromTopTranslationY_whenNotOnFold() = testScope.runTest { + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.POWER_BUTTON, + powerButtonLaunchGestureTriggered = false + ) + val pixels = -100f val enterFromTopTranslationY by collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt())) @@ -88,6 +99,80 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { } @Test + fun enterFromTopTranslationY_whenOnFold_emitsNothing() = + testScope.runTest { + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.FOLD, + powerButtonLaunchGestureTriggered = false + ) + + val pixels = -100f + val enterFromTopTranslationY by + collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt())) + runCurrent() + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(enterFromTopTranslationY).isNull() + + repository.sendTransitionStep(step(.55f)) + assertThat(enterFromTopTranslationY).isNull() + + repository.sendTransitionStep(step(.85f)) + assertThat(enterFromTopTranslationY).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(enterFromTopTranslationY).isNull() + } + + @Test + fun enterFromSideTranslationX_onFold() = + testScope.runTest { + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.FOLD, + powerButtonLaunchGestureTriggered = false + ) + + val pixels = -100f + val enterFromSideTranslationX by + collectLastValue(underTest.enterFromSideTranslationX(pixels.toInt())) + runCurrent() + + // The animation should only start > .4f way through + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(enterFromSideTranslationX) + .isEqualTo( + StateToValue( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + value = pixels + ) + ) + + repository.sendTransitionStep(step(.55f)) + assertThat(enterFromSideTranslationX!!.value ?: -1f).isIn(Range.closed(pixels, 0f)) + + repository.sendTransitionStep(step(.85f)) + assertThat(enterFromSideTranslationX!!.value ?: -1f).isIn(Range.closed(pixels, 0f)) + + // At the end, the translation should be complete and set to zero + repository.sendTransitionStep(step(1f)) + assertThat(enterFromSideTranslationX) + .isEqualTo( + StateToValue( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.RUNNING, + value = 0f + ) + ) + } + + @Test fun enterFromTopAnimationAlpha() = testScope.runTest { val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt index 3777e40b5126..6f74ed34c4e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt @@ -16,98 +16,108 @@ package com.android.systemui.keyguard.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.annotations.DisableFlags +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.domain.interactor.keyguardBottomAreaInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mock -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidJUnit4::class) -class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper - @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel - - @Mock private lateinit var burnInInteractor: BurnInInteractor - private val burnInFlow = MutableStateFlow(BurnInModel()) - - private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor + private val bottomAreaInteractor = kosmos.keyguardBottomAreaInteractor private lateinit var underTest: KeyguardIndicationAreaViewModel - private lateinit var repository: FakeKeyguardRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val communalSceneRepository = kosmos.fakeCommunalSceneRepository private val startButtonFlow = - MutableStateFlow<KeyguardQuickAffordanceViewModel>( + MutableStateFlow( KeyguardQuickAffordanceViewModel( slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId() ) ) private val endButtonFlow = - MutableStateFlow<KeyguardQuickAffordanceViewModel>( + MutableStateFlow( KeyguardQuickAffordanceViewModel( slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId() ) ) - private val alphaFlow = MutableStateFlow<Float>(1f) + private val alphaFlow = MutableStateFlow(1f) + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Before fun setUp() { - MockitoAnnotations.initMocks(this) - - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - - whenever(burnInHelperWrapper.burnInOffset(anyInt(), any())) - .thenReturn(RETURNED_BURN_IN_OFFSET) - whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) - - val withDeps = KeyguardInteractorFactory.create() - val keyguardInteractor = withDeps.keyguardInteractor - repository = withDeps.repository - - val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock() - whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow) - whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow) - whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow) - bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository) + val bottomAreaViewModel = + mock<KeyguardBottomAreaViewModel> { + on { startButton } doReturn startButtonFlow + on { endButton } doReturn endButtonFlow + on { alpha } doReturn alphaFlow + } + val burnInInteractor = + mock<BurnInInteractor> { + on { burnIn(anyInt(), anyInt()) } doReturn flowOf(BurnInModel()) + } + val burnInHelperWrapper = + mock<BurnInHelperWrapper> { + on { burnInOffset(anyInt(), any()) } doReturn RETURNED_BURN_IN_OFFSET + } + val shortcutsCombinedViewModel = + mock<KeyguardQuickAffordancesCombinedViewModel> { + on { startButton } doReturn startButtonFlow + on { endButton } doReturn endButtonFlow + } underTest = KeyguardIndicationAreaViewModel( - keyguardInteractor = keyguardInteractor, + keyguardInteractor = kosmos.keyguardInteractor, bottomAreaInteractor = bottomAreaInteractor, keyguardBottomAreaViewModel = bottomAreaViewModel, burnInHelperWrapper = burnInHelperWrapper, burnInInteractor = burnInInteractor, shortcutsCombinedViewModel = shortcutsCombinedViewModel, - configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), + configurationInteractor = kosmos.configurationInteractor, keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, - backgroundCoroutineContext = kosmos.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, + communalSceneInteractor = kosmos.communalSceneInteractor, mainDispatcher = kosmos.testDispatcher ) } @@ -115,77 +125,120 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { @Test fun alpha() = testScope.runTest { - val value = collectLastValue(underTest.alpha) + val alpha by collectLastValue(underTest.alpha) - assertThat(value()).isEqualTo(1f) + assertThat(alpha).isEqualTo(1f) alphaFlow.value = 0.1f - assertThat(value()).isEqualTo(0.1f) + assertThat(alpha).isEqualTo(0.1f) alphaFlow.value = 0.5f - assertThat(value()).isEqualTo(0.5f) + assertThat(alpha).isEqualTo(0.5f) alphaFlow.value = 0.2f - assertThat(value()).isEqualTo(0.2f) + assertThat(alpha).isEqualTo(0.2f) alphaFlow.value = 0f - assertThat(value()).isEqualTo(0f) + assertThat(alpha).isEqualTo(0f) } @Test + @DisableFlags(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) fun isIndicationAreaPadded() = testScope.runTest { - repository.setKeyguardShowing(true) - val value = collectLastValue(underTest.isIndicationAreaPadded) + keyguardRepository.setKeyguardShowing(true) + val isIndicationAreaPadded by collectLastValue(underTest.isIndicationAreaPadded) - assertThat(value()).isFalse() + assertThat(isIndicationAreaPadded).isFalse() startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) - assertThat(value()).isTrue() + assertThat(isIndicationAreaPadded).isTrue() endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) - assertThat(value()).isTrue() + assertThat(isIndicationAreaPadded).isTrue() startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) - assertThat(value()).isTrue() + assertThat(isIndicationAreaPadded).isTrue() endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) - assertThat(value()).isFalse() + assertThat(isIndicationAreaPadded).isFalse() } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) fun indicationAreaTranslationX() = testScope.runTest { - val value = collectLastValue(underTest.indicationAreaTranslationX) + val translationX by collectLastValue(underTest.indicationAreaTranslationX) - assertThat(value()).isEqualTo(0f) + assertThat(translationX).isEqualTo(0f) bottomAreaInteractor.setClockPosition(100, 100) - assertThat(value()).isEqualTo(100f) + assertThat(translationX).isEqualTo(100f) bottomAreaInteractor.setClockPosition(200, 100) - assertThat(value()).isEqualTo(200f) + assertThat(translationX).isEqualTo(200f) bottomAreaInteractor.setClockPosition(200, 200) - assertThat(value()).isEqualTo(200f) + assertThat(translationX).isEqualTo(200f) bottomAreaInteractor.setClockPosition(300, 100) - assertThat(value()).isEqualTo(300f) + assertThat(translationX).isEqualTo(300f) } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun indicationAreaTranslationY() = testScope.runTest { - val value = + val translationY by collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) // Negative 0 - apparently there's a difference in floating point arithmetic - FML - assertThat(value()).isEqualTo(-0f) + assertThat(translationY).isEqualTo(-0f) val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) - assertThat(value()).isEqualTo(expected1) + assertThat(translationY).isEqualTo(expected1) val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) - assertThat(value()).isEqualTo(expected2) + assertThat(translationY).isEqualTo(expected2) val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) - assertThat(value()).isEqualTo(expected3) + assertThat(translationY).isEqualTo(expected3) val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) - assertThat(value()).isEqualTo(expected4) + assertThat(translationY).isEqualTo(expected4) + } + + @Test + fun visibilityWhenCommunalNotShowing() = + testScope.runTest { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + val visible by collectLastValue(underTest.visible) + + assertThat(visible).isTrue() + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + assertThat(visible).isFalse() + } + + @Test + fun visibilityWhenCommunalShowing() = + testScope.runTest { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + communalSceneRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) + ) + + val visible by collectLastValue(underTest.visible) + + assertThat(visible).isTrue() + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + assertThat(visible).isTrue() + + communalSceneRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(CommunalScenes.Blank)) + ) + assertThat(visible).isFalse() } private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { - repository.setDozeAmount(dozeAmount) + keyguardRepository.setDozeAmount(dozeAmount) return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET) } companion object { private const val DEFAULT_BURN_IN_OFFSET = 5 private const val RETURNED_BURN_IN_OFFSET = 3 + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf( + FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, + FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + ) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt index bc0512a1468d..f43fa5048298 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt @@ -30,11 +30,21 @@ import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel +import com.android.systemui.media.controls.util.SmallHash +import com.android.systemui.media.controls.util.mediaSmartspaceLogger +import com.android.systemui.media.controls.util.mockMediaSmartspaceLogger import com.android.systemui.testKosmos +import com.android.systemui.util.time.systemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.kotlin.never +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) @@ -42,8 +52,20 @@ class MediaFilterRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope + private val smartspaceLogger = kosmos.mockMediaSmartspaceLogger + private val icon = Icon.createWithResource(context, R.drawable.ic_media_play) + private val mediaRecommendation = + SmartspaceMediaData( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + recommendations = MediaTestHelper.getValidRecommendationList(icon), + ) - private val underTest: MediaFilterRepository = kosmos.mediaFilterRepository + private val underTest: MediaFilterRepository = + with(kosmos) { + mediaSmartspaceLogger = mockMediaSmartspaceLogger + mediaFilterRepository + } @Test fun addSelectedUserMediaEntry_activeThenInactivate() = @@ -137,14 +159,6 @@ class MediaFilterRepositoryTest : SysuiTestCase() { testScope.runTest { val smartspaceMediaData by collectLastValue(underTest.smartspaceMediaData) - val icon = Icon.createWithResource(context, R.drawable.ic_media_play) - val mediaRecommendation = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) - underTest.setRecommendation(mediaRecommendation) assertThat(smartspaceMediaData).isEqualTo(mediaRecommendation) @@ -164,16 +178,38 @@ class MediaFilterRepositoryTest : SysuiTestCase() { val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId) val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId) + underTest.setRecommendation(mediaRecommendation) + underTest.setRecommendationsLoadingState( + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) + ) underTest.addSelectedUserMediaEntry(playingData) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId)) + + verify(smartspaceLogger) + .logSmartspaceCardReceived( + playingData.smartspaceId, + playingData.appUid, + cardinality = 2 + ) + underTest.addSelectedUserMediaEntry(remoteData) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId)) - assertThat(currentMedia?.size).isEqualTo(2) + verify(smartspaceLogger) + .logSmartspaceCardReceived( + remoteData.smartspaceId, + playingData.appUid, + cardinality = 3, + rank = 1 + ) + assertThat(currentMedia?.size).isEqualTo(3) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId)), - MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId)) + MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId)), + MediaCommonModel.MediaRecommendations( + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) + ) ) .inOrder() } @@ -222,6 +258,16 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.setOrderedMedia() + verify(smartspaceLogger, never()) + .logSmartspaceCardReceived( + anyInt(), + anyInt(), + anyInt(), + anyBoolean(), + anyBoolean(), + anyInt(), + anyInt() + ) assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( @@ -248,14 +294,6 @@ class MediaFilterRepositoryTest : SysuiTestCase() { val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4) val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5) - val icon = Icon.createWithResource(context, R.drawable.ic_media_play) - val mediaRecommendations = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) - underTest.addSelectedUserMediaEntry(stoppedAndLocalData) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId3)) @@ -271,11 +309,33 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(playingAndRemoteData) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2)) - underTest.setRecommendation(mediaRecommendations) + underTest.setRecommendation(mediaRecommendation) underTest.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) ) + underTest.setOrderedMedia() + val smartspaceId = SmallHash.hash(mediaRecommendation.targetId) + verify(smartspaceLogger) + .logSmartspaceCardReceived( + eq(smartspaceId), + anyInt(), + eq(6), + anyBoolean(), + anyBoolean(), + eq(2), + anyInt() + ) + verify(smartspaceLogger, never()) + .logSmartspaceCardReceived( + eq(playingAndLocalData.smartspaceId), + anyInt(), + anyInt(), + anyBoolean(), + anyBoolean(), + anyInt(), + anyInt() + ) assertThat(currentMedia?.size).isEqualTo(6) assertThat(currentMedia) .containsExactly( @@ -312,18 +372,10 @@ class MediaFilterRepositoryTest : SysuiTestCase() { isPlaying = true, notificationKey = KEY_2 ) - val icon = Icon.createWithResource(context, R.drawable.ic_media_play) - val mediaRecommendations = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - packageName = PACKAGE_NAME, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) underTest.setMediaFromRecPackageName(PACKAGE_NAME) underTest.addSelectedUserMediaEntry(data) - underTest.setRecommendation(mediaRecommendations) + underTest.setRecommendation(mediaRecommendation) underTest.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) ) @@ -365,6 +417,88 @@ class MediaFilterRepositoryTest : SysuiTestCase() { fun hasActiveMedia_noMediaSet_returnsFalse() = testScope.runTest { assertThat(underTest.hasActiveMedia()).isFalse() } + @Test + fun updateMediaWithLatency_smartspaceIsLogged() = + testScope.runTest { + val instanceId = InstanceId.fakeInstanceId(123) + val data = createMediaData("app", true, LOCAL, false, instanceId) + + underTest.setRecommendation(mediaRecommendation) + underTest.setRecommendationsLoadingState( + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) + ) + + val smartspaceId = SmallHash.hash(mediaRecommendation.targetId) + verify(smartspaceLogger) + .logSmartspaceCardReceived( + eq(smartspaceId), + anyInt(), + eq(1), + eq(true), + anyBoolean(), + eq(0), + anyInt() + ) + reset(smartspaceLogger) + + underTest.addSelectedUserMediaEntry(data) + underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) + + verify(smartspaceLogger) + .logSmartspaceCardReceived(data.smartspaceId, data.appUid, cardinality = 2) + + reset(smartspaceLogger) + + underTest.addSelectedUserMediaEntry(data) + underTest.addMediaDataLoadingState( + MediaDataLoadingModel.Loaded(instanceId, receivedSmartspaceCardLatency = 123) + ) + + verify(smartspaceLogger) + .logSmartspaceCardReceived( + SmallHash.hash(data.appUid + kosmos.systemClock.currentTimeMillis().toInt()), + data.appUid, + cardinality = 2, + rank = 0, + receivedLatencyMillis = 123 + ) + } + + @Test + fun resumeMedia_loadSmartspace_allSmartspaceIsLogged() = + testScope.runTest { + val resumeInstanceId = InstanceId.fakeInstanceId(123) + val data = createMediaData("app", false, LOCAL, true, resumeInstanceId) + + underTest.addSelectedUserMediaEntry(data.copy(active = false)) + underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(resumeInstanceId)) + underTest.setRecommendation(mediaRecommendation) + underTest.setRecommendationsLoadingState( + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) + ) + + assertThat(underTest.hasActiveMedia()).isFalse() + assertThat(underTest.hasAnyMedia()).isTrue() + val smartspaceId = SmallHash.hash(mediaRecommendation.targetId) + verify(smartspaceLogger) + .logSmartspaceCardReceived( + eq(smartspaceId), + anyInt(), + eq(2), + eq(true), + anyBoolean(), + eq(0), + anyInt() + ) + verify(smartspaceLogger) + .logSmartspaceCardReceived( + SmallHash.hash(data.appUid + kosmos.systemClock.currentTimeMillis().toInt()), + data.appUid, + cardinality = 2, + rank = 1 + ) + } + private fun createMediaData( app: String, playing: Boolean, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/data/repository/NavigationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/data/repository/NavigationRepositoryTest.kt new file mode 100644 index 000000000000..e45aa05408e7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/data/repository/NavigationRepositoryTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.navigation.data.repository + +import android.view.WindowManagerPolicyConstants +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener +import com.android.systemui.navigationbar.navigationModeController +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NavigationRepositoryTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val navigationModeControllerMock = kosmos.navigationModeController + + private val underTest = kosmos.navigationRepository + + private var currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON + private val modeChangedListeners = mutableListOf<ModeChangedListener>() + + @Before + fun setUp() { + whenever(navigationModeControllerMock.addListener(any())).thenAnswer { invocation -> + val listener = invocation.arguments[0] as ModeChangedListener + modeChangedListeners.add(listener) + currentMode + } + } + + @Test + fun isGesturalMode() = + testScope.runTest { + val isGesturalMode by collectLastValue(underTest.isGesturalMode) + assertThat(isGesturalMode).isFalse() + + currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL + notifyModeChangedListeners() + assertThat(isGesturalMode).isTrue() + + currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON + notifyModeChangedListeners() + assertThat(isGesturalMode).isFalse() + } + + private fun notifyModeChangedListeners() { + modeChangedListeners.forEach { listener -> listener.onNavigationModeChanged(currentMode) } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorTest.kt new file mode 100644 index 000000000000..88beeb2b0d8a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.navigation.domain.interactor + +import android.view.WindowManagerPolicyConstants +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener +import com.android.systemui.navigationbar.navigationModeController +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NavigationInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val navigationModeControllerMock = kosmos.navigationModeController + + private val underTest = kosmos.navigationInteractor + + private var currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON + private val modeChangedListeners = mutableListOf<ModeChangedListener>() + + @Before + fun setUp() { + whenever(navigationModeControllerMock.addListener(any())).thenAnswer { invocation -> + val listener = invocation.arguments[0] as ModeChangedListener + modeChangedListeners.add(listener) + currentMode + } + } + + @Test + fun isGesturalMode() = + testScope.runTest { + val isGesturalMode by collectLastValue(underTest.isGesturalMode) + assertThat(isGesturalMode).isFalse() + + currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL + notifyModeChangedListeners() + assertThat(isGesturalMode).isTrue() + + currentMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON + notifyModeChangedListeners() + assertThat(isGesturalMode).isFalse() + } + + private fun notifyModeChangedListeners() { + modeChangedListeners.forEach { listener -> listener.onNavigationModeChanged(currentMode) } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt new file mode 100644 index 000000000000..14d60943149f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class PaginatedGridRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + + val underTest = kosmos.paginatedGridRepository + + @Test + fun rows_followsConfig() = + with(kosmos) { + testScope.runTest { + val rows by collectLastValue(underTest.rows) + + setRowsInConfig(3) + assertThat(rows).isEqualTo(3) + + setRowsInConfig(6) + assertThat(rows).isEqualTo(6) + } + } + + private fun setRowsInConfig(rows: Int) = + with(kosmos) { + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_max_rows, + rows, + ) + fakeConfigurationRepository.onConfigurationChange() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt new file mode 100644 index 000000000000..914a09597d65 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.data.repository.IconTilesRepository +import com.android.systemui.qs.panels.data.repository.iconTilesRepository +import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel +import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class InfiniteGridLayoutTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + iconTilesRepository = + object : IconTilesRepository { + override fun isIconTile(spec: TileSpec): Boolean { + return spec.spec.startsWith("small") + } + } + } + + private val underTest = + with(kosmos) { + InfiniteGridLayout( + iconTilesViewModel, + fixedColumnsSizeViewModel, + ) + } + + @Test + fun correctPagination_underOnePage_sameOrder() = + with(kosmos) { + testScope.runTest { + val rows = 3 + val columns = 4 + + val tiles = + listOf( + largeTile(), + smallTile(), + smallTile(), + largeTile(), + largeTile(), + smallTile() + ) + + val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) + + assertThat(pages).hasSize(1) + assertThat(pages[0]).isEqualTo(tiles) + } + } + + @Test + fun correctPagination_twoPages_sameOrder() = + with(kosmos) { + testScope.runTest { + val rows = 3 + val columns = 4 + + val tiles = + listOf( + largeTile(), + smallTile(), + smallTile(), + largeTile(), + largeTile(), + smallTile(), + smallTile(), + largeTile(), + largeTile(), + smallTile(), + smallTile(), + largeTile(), + ) + // --- Page 1 --- + // [L L] [S] [S] + // [L L] [L L] + // [S] [S] [L L] + // --- Page 2 --- + // [L L] [S] [S] + // [L L] + + val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) + + assertThat(pages).hasSize(2) + assertThat(pages[0]).isEqualTo(tiles.take(8)) + assertThat(pages[1]).isEqualTo(tiles.drop(8)) + } + } + + companion object { + fun largeTile() = MockTileViewModel(TileSpec.create("large")) + + fun smallTile() = MockTileViewModel(TileSpec.create("small")) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt new file mode 100644 index 000000000000..6df3f8d1bdd5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class PaginatableGridLayoutTest : SysuiTestCase() { + @Test + fun correctRows_gapsAtEnd() { + val columns = 6 + + val sizedTiles = + listOf( + largeTile(), + extraLargeTile(), + largeTile(), + smallTile(), + largeTile(), + ) + + // [L L] [XL XL XL] + // [L L] [S] [L L] + + val rows = PaginatableGridLayout.splitInRows(sizedTiles, columns) + + assertThat(rows).hasSize(2) + assertThat(rows[0]).isEqualTo(sizedTiles.take(2)) + assertThat(rows[1]).isEqualTo(sizedTiles.drop(2)) + } + + @Test + fun correctRows_fullLastRow_noEmptyRow() { + val columns = 6 + + val sizedTiles = + listOf( + largeTile(), + extraLargeTile(), + smallTile(), + ) + + // [L L] [XL XL XL] [S] + + val rows = PaginatableGridLayout.splitInRows(sizedTiles, columns) + + assertThat(rows).hasSize(1) + assertThat(rows[0]).isEqualTo(sizedTiles) + } + + companion object { + fun extraLargeTile() = SizedTile(MockTileViewModel(TileSpec.create("XLarge")), 3) + + fun largeTile() = SizedTile(MockTileViewModel(TileSpec.create("large")), 2) + + fun smallTile() = SizedTile(MockTileViewModel(TileSpec.create("small")), 1) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt new file mode 100644 index 000000000000..3354b4d4116b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.data.repository.IconTilesRepository +import com.android.systemui.qs.panels.data.repository.iconTilesRepository +import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel +import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.testKosmos +import com.google.common.truth.Truth +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class PartitionedGridLayoutTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + iconTilesRepository = + object : IconTilesRepository { + override fun isIconTile(spec: TileSpec): Boolean { + return spec.spec.startsWith("small") + } + } + } + + private val underTest = with(kosmos) { PartitionedGridLayout(partitionedGridViewModel) } + + @Test + fun correctPagination_underOnePage_partitioned_sameRelativeOrder() = + with(kosmos) { + testScope.runTest { + val rows = 3 + val columns = 4 + + val tiles = + listOf( + largeTile(), + smallTile(), + smallTile(), + largeTile(), + largeTile(), + smallTile() + ) + val (smallTiles, largeTiles) = + tiles.partition { iconTilesViewModel.isIconTile(it.spec) } + + // [L L] [L L] + // [L L] + // [S] [S] [S] + + val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) + + Truth.assertThat(pages).hasSize(1) + Truth.assertThat(pages[0]).isEqualTo(largeTiles + smallTiles) + } + } + + @Test + fun correctPagination_twoPages_partitioned_sameRelativeOrder() = + with(kosmos) { + testScope.runTest { + val rows = 3 + val columns = 4 + + val tiles = + listOf( + largeTile(), + smallTile(), + smallTile(), + largeTile(), + smallTile(), + smallTile(), + largeTile(), + smallTile(), + smallTile(), + ) + // --- Page 1 --- + // [L L] [L L] + // [L L] + // [S] [S] [S] [S] + // --- Page 2 --- + // [S] [S] + + val (smallTiles, largeTiles) = + tiles.partition { iconTilesViewModel.isIconTile(it.spec) } + + val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) + + val expectedPage0 = largeTiles + smallTiles.take(4) + val expectedPage1 = smallTiles.drop(4) + + Truth.assertThat(pages).hasSize(2) + Truth.assertThat(pages[0]).isEqualTo(expectedPage0) + Truth.assertThat(pages[1]).isEqualTo(expectedPage1) + } + } + + companion object { + fun largeTile() = MockTileViewModel(TileSpec.create("large")) + + fun smallTile() = MockTileViewModel(TileSpec.create("small")) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt index 9bb591e1ae7c..8ad647dd4d26 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.CastTile import com.android.systemui.statusbar.policy.CastController +import com.android.systemui.statusbar.policy.CastDevice import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -73,9 +74,12 @@ class CastAutoAddableTest : SysuiTestCase() { @Test fun onCastDevicesChanged_deviceNotConnectedOrConnecting_noSignal() = runTest { val device = - CastController.CastDevice().apply { - state = CastController.CastDevice.STATE_DISCONNECTED - } + CastDevice( + id = "id", + name = null, + state = CastDevice.CastState.Disconnected, + origin = CastDevice.CastOrigin.MediaProjection, + ) whenever(castController.castDevices).thenReturn(listOf(device)) val signal by collectLastValue(underTest.autoAddSignal(0)) @@ -91,11 +95,19 @@ class CastAutoAddableTest : SysuiTestCase() { @Test fun onCastDevicesChanged_someDeviceConnecting_addSignal() = runTest { val disconnectedDevice = - CastController.CastDevice().apply { - state = CastController.CastDevice.STATE_DISCONNECTED - } + CastDevice( + id = "id", + name = null, + state = CastDevice.CastState.Disconnected, + origin = CastDevice.CastOrigin.MediaProjection, + ) val connectingDevice = - CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTING } + CastDevice( + id = "id", + name = null, + state = CastDevice.CastState.Connecting, + origin = CastDevice.CastOrigin.MediaProjection, + ) whenever(castController.castDevices) .thenReturn(listOf(disconnectedDevice, connectingDevice)) @@ -112,11 +124,19 @@ class CastAutoAddableTest : SysuiTestCase() { @Test fun onCastDevicesChanged_someDeviceConnected_addSignal() = runTest { val disconnectedDevice = - CastController.CastDevice().apply { - state = CastController.CastDevice.STATE_DISCONNECTED - } + CastDevice( + id = "id", + name = null, + state = CastDevice.CastState.Disconnected, + origin = CastDevice.CastOrigin.MediaProjection, + ) val connectedDevice = - CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTED } + CastDevice( + id = "id", + name = null, + state = CastDevice.CastState.Connected, + origin = CastDevice.CastOrigin.MediaProjection, + ) whenever(castController.castDevices).thenReturn(listOf(disconnectedDevice, connectedDevice)) val signal by collectLastValue(underTest.autoAddSignal(0)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt index b7e08da42afd..ff40e43e2c8c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt @@ -54,7 +54,7 @@ class QSTileLoggerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer) + whenever(logBufferFactory.create(any(), any(), any(), any())).thenReturn(logBuffer) val tileSpec: TileSpec = TileSpec.create("chatty_tile") underTest = QSTileLogger(mapOf(tileSpec to chattyLogBuffer), logBufferFactory, statusBarController) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractorTest.kt new file mode 100644 index 000000000000..2194c75dba5b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractorTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.irecording + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.recordissue.IssueRecordingState +import com.android.systemui.settings.fakeUserFileManager +import com.android.systemui.settings.userTracker +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class IssueRecordingDataInteractorTest : SysuiTestCase() { + + private val kosmos = Kosmos().also { it.testCase = this } + private val userTracker = kosmos.userTracker + private val userFileManager = kosmos.fakeUserFileManager + private val testUser = UserHandle.of(1) + + lateinit var state: IssueRecordingState + private lateinit var underTest: IssueRecordingDataInteractor + + @Before + fun setup() { + state = IssueRecordingState(userTracker, userFileManager) + underTest = IssueRecordingDataInteractor(state, kosmos.testScope.testScheduler) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun emitsEvent_whenIsRecordingStatusChanges_correctly() { + kosmos.testScope.runTest { + val data by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + Truth.assertThat(data?.isRecording).isFalse() + + state.isRecording = true + runCurrent() + Truth.assertThat(data?.isRecording).isTrue() + + state.isRecording = false + runCurrent() + Truth.assertThat(data?.isRecording).isFalse() + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt new file mode 100644 index 000000000000..244422943309 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.irecording + +import android.content.res.mainResources +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.recordissue.RecordIssueModule +import com.android.systemui.res.R +import com.google.common.truth.Truth +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class IssueRecordingMapperTest : SysuiTestCase() { + private val kosmos = Kosmos().also { it.testCase = this } + private val uiConfig = + QSTileUIConfig.Resource(R.drawable.qs_record_issue_icon_off, R.string.qs_record_issue_label) + private val config = + QSTileConfig( + TileSpec.create(RecordIssueModule.TILE_SPEC), + uiConfig, + kosmos.qsEventLogger.getNewInstanceId() + ) + private val resources = kosmos.mainResources + private val theme = resources.newTheme() + + @Test + fun whenData_isRecording_useCorrectResources() { + val underTest = IssueRecordingMapper(resources, theme) + val tileState = underTest.map(config, IssueRecordingModel(true)) + Truth.assertThat(tileState.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) + } + + @Test + fun whenData_isNotRecording_useCorrectResources() { + val underTest = IssueRecordingMapper(resources, theme) + val tileState = underTest.map(config, IssueRecordingModel(false)) + Truth.assertThat(tileState.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt new file mode 100644 index 000000000000..4e5806902a10 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.irecording + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.dialogTransitionAnimator +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.activityStarter +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.recordissue.RecordIssueDialogDelegate +import com.android.systemui.settings.UserContextProvider +import com.android.systemui.settings.userTracker +import com.android.systemui.statusbar.phone.KeyguardDismissUtil +import com.android.systemui.statusbar.policy.keyguardStateController +import com.google.common.truth.Truth +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +class IssueRecordingUserActionInteractorTest : SysuiTestCase() { + + val user = UserHandle(1) + val kosmos = Kosmos().also { it.testCase = this } + + private lateinit var userContextProvider: UserContextProvider + private lateinit var underTest: IssueRecordingUserActionInteractor + + private var hasCreatedDialogDelegate: Boolean = false + + @Before + fun setup() { + hasCreatedDialogDelegate = false + with(kosmos) { + val factory = + object : RecordIssueDialogDelegate.Factory { + override fun create(onStarted: Runnable): RecordIssueDialogDelegate { + hasCreatedDialogDelegate = true + + // Inside some tests in presubmit, createDialog throws an error because + // the test thread's looper hasn't been prepared, and Dialog.class + // internally is creating a new handler. For testing, we only care that the + // dialog is created, so using a mock is acceptable here. + return mock(RecordIssueDialogDelegate::class.java) + } + } + + userContextProvider = userTracker + underTest = + IssueRecordingUserActionInteractor( + testDispatcher, + KeyguardDismissUtil( + keyguardStateController, + statusBarStateController, + activityStarter + ), + keyguardStateController, + dialogTransitionAnimator, + panelInteractor, + userTracker, + factory + ) + } + } + + @Test + fun handleInput_showsPromptToStartRecording_whenNotRecordingAlready() { + kosmos.testScope.runTest { + underTest.handleInput( + QSTileInput(user, QSTileUserAction.Click(null), IssueRecordingModel(false)) + ) + Truth.assertThat(hasCreatedDialogDelegate).isTrue() + } + } + + @Test + fun handleInput_attemptsToStopRecording_whenRecording() { + kosmos.testScope.runTest { + val input = QSTileInput(user, QSTileUserAction.Click(null), IssueRecordingModel(true)) + try { + underTest.handleInput(input) + } catch (e: NullPointerException) { + // As of 06/07/2024, PendingIntent.startService is not easily mockable and throws + // an NPE inside IActivityManager. Catching that here and ignore it, then verify + // mock interactions were done correctly + } + Truth.assertThat(hasCreatedDialogDelegate).isFalse() + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 5b6fea5a52f0..d43d50ab0dc4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -42,9 +42,9 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneBackInteractor -import com.android.systemui.scene.domain.interactor.sceneContainerStartable import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 4d5d22c11f71..412505d50fc9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -58,9 +58,9 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.qs.footerActionsController import com.android.systemui.qs.footerActionsViewModelFactory import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter -import com.android.systemui.scene.domain.interactor.sceneContainerStartable import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt index e3108ad1b8f1..1f3454de14d7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index ec7150b5af2d..5242fe33a281 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.Idle @@ -450,4 +451,16 @@ class SceneInteractorTest : SysuiTestCase() { progress.value = 0.9f assertThat(transitionValue).isEqualTo(0f) } + + @Test + fun changeScene_toGone_whenKeyguardDisabled_doesNotThrow() = + testScope.runTest { + val currentScene by collectLastValue(underTest.currentScene) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false) + + underTest.changeScene(Scenes.Gone, "") + + assertThat(currentScene).isEqualTo(Scenes.Gone) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableTest.kt new file mode 100644 index 000000000000..695edafefdf3 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableTest.kt @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.internal.policy.IKeyguardStateCallback +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeTrustRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableSceneContainer +class KeyguardStateCallbackStartableTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val underTest = kosmos.keyguardStateCallbackStartable + + @Test + fun addCallback_hydratesAllWithCurrentState() = + testScope.runTest { + val testState = setUpTest() + val callback = mockCallback() + + underTest.addCallback(callback) + runCurrent() + + with(testState) { + val captor = argumentCaptor<Boolean>() + verify(callback, atLeastOnce()).onShowingStateChanged(captor.capture(), eq(userId)) + assertThat(captor.lastValue).isEqualTo(isKeyguardShowing) + verify(callback, atLeastOnce()).onInputRestrictedStateChanged(captor.capture()) + assertThat(captor.lastValue).isEqualTo(isInputRestricted) + verify(callback, atLeastOnce()).onSimSecureStateChanged(captor.capture()) + assertThat(captor.lastValue).isEqualTo(isSimSecure) + verify(callback, atLeastOnce()).onTrustedChanged(captor.capture()) + assertThat(captor.lastValue).isEqualTo(isTrusted) + } + } + + @Test + fun hydrateKeyguardShowingState() = + testScope.runTest { + setUpTest(isKeyguardShowing = true) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + verify(callback, atLeastOnce()).onShowingStateChanged(eq(true), anyInt()) + + unlockDevice() + runCurrent() + + verify(callback).onShowingStateChanged(eq(false), anyInt()) + } + + @Test + fun hydrateInputRestrictedState() = + testScope.runTest { + setUpTest(isKeyguardShowing = true) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + val captor = argumentCaptor<Boolean>() + verify(callback, atLeastOnce()).onInputRestrictedStateChanged(captor.capture()) + assertThat(captor.lastValue).isTrue() + + unlockDevice() + runCurrent() + + verify(callback, atLeastOnce()).onInputRestrictedStateChanged(captor.capture()) + assertThat(captor.lastValue).isFalse() + } + + @Test + fun hydrateSimSecureState() = + testScope.runTest { + setUpTest(isSimSecure = false) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + val captor = argumentCaptor<Boolean>() + verify(callback, atLeastOnce()).onSimSecureStateChanged(captor.capture()) + assertThat(captor.lastValue).isFalse() + + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true + runCurrent() + + verify(callback, atLeastOnce()).onSimSecureStateChanged(captor.capture()) + assertThat(captor.lastValue).isTrue() + } + + @Test + fun notifyWhenKeyguardShowingChanged() = + testScope.runTest { + setUpTest(isKeyguardShowing = true) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + assertThat(kosmos.fakeTrustRepository.keyguardShowingChangeEventCount).isEqualTo(1) + + unlockDevice() + runCurrent() + + assertThat(kosmos.fakeTrustRepository.keyguardShowingChangeEventCount).isEqualTo(2) + } + + @Test + fun notifyWhenTrustChanged() = + testScope.runTest { + setUpTest(isTrusted = false) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + val captor = argumentCaptor<Boolean>() + verify(callback, atLeastOnce()).onTrustedChanged(captor.capture()) + assertThat(captor.lastValue).isFalse() + + kosmos.fakeTrustRepository.setCurrentUserTrusted(true) + runCurrent() + + verify(callback, atLeastOnce()).onTrustedChanged(captor.capture()) + assertThat(captor.lastValue).isTrue() + } + + private suspend fun TestScope.setUpTest( + isKeyguardShowing: Boolean = true, + userId: Int = selectedUser.id, + isInputRestricted: Boolean = true, + isSimSecure: Boolean = false, + isTrusted: Boolean = false, + ): TestState { + val testState = + TestState( + isKeyguardShowing = isKeyguardShowing, + userId = userId, + isInputRestricted = isInputRestricted, + isSimSecure = isSimSecure, + isTrusted = isTrusted, + ) + + if (isKeyguardShowing) { + lockDevice() + } else { + unlockDevice() + } + + kosmos.fakeUserRepository.setUserInfos(listOf(selectedUser)) + kosmos.fakeUserRepository.setSelectedUserInfo(selectedUser) + + if (isInputRestricted && !isKeyguardShowing) { + // TODO(b/348644111): add support for mNeedToReshowWhenReenabled + } else if (!isInputRestricted) { + assertWithMessage( + "If isInputRestricted is false, isKeyguardShowing must also be false!" + ) + .that(isKeyguardShowing) + .isFalse() + } + + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = isSimSecure + + kosmos.fakeTrustRepository.setCurrentUserTrusted(isTrusted) + + runCurrent() + + underTest.start() + + return testState + } + + private fun lockDevice() { + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen)) + kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "") + } + + private fun unlockDevice() { + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + } + + private fun mockCallback(): IKeyguardStateCallback { + return mock() + } + + private data class TestState( + val isKeyguardShowing: Boolean, + val userId: Int, + val isInputRestricted: Boolean, + val isSimSecure: Boolean, + val isTrusted: Boolean, + ) + + companion object { + private val selectedUser = + UserInfo( + /* id= */ 100, + /* name= */ "First user", + /* flags= */ 0, + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index e40c8eecca0f..9edc3af6bb4e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -51,7 +51,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState -import com.android.systemui.scene.domain.interactor.sceneContainerStartable import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt new file mode 100644 index 000000000000..9601f20add8e --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import android.app.StatusBarManager +import android.provider.DeviceConfig +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.internal.statusbar.statusBarService +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.navigationbar.navigationModeController +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor +import com.android.systemui.testKosmos +import com.android.systemui.util.fakeDeviceConfigProxy +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlin.reflect.full.memberProperties +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.Parameter +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +@EnableSceneContainer +class StatusBarStartableTest : SysuiTestCase() { + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun testSpecs(): List<TestSpec> { + return listOf( + TestSpec( + id = 0, + expectedFlags = StatusBarManager.DISABLE_NONE, + Preconditions( + isForceHideHomeAndRecents = false, + isKeyguardShowing = false, + isPowerGestureIntercepted = false, + ), + ), + TestSpec( + id = 1, + expectedFlags = StatusBarManager.DISABLE_NONE, + Preconditions( + isForceHideHomeAndRecents = false, + isKeyguardShowing = true, + isOccluded = true, + isPowerGestureIntercepted = false, + ), + ), + TestSpec( + id = 2, + expectedFlags = StatusBarManager.DISABLE_NONE, + Preconditions( + isForceHideHomeAndRecents = false, + isKeyguardShowing = false, + isPowerGestureIntercepted = true, + isOccluded = false, + ), + ), + TestSpec( + id = 3, + expectedFlags = StatusBarManager.DISABLE_NONE, + Preconditions( + isForceHideHomeAndRecents = false, + isKeyguardShowing = true, + isOccluded = true, + isPowerGestureIntercepted = true, + isAuthenticationMethodSecure = false, + ), + ), + TestSpec( + id = 4, + expectedFlags = StatusBarManager.DISABLE_NONE, + Preconditions( + isForceHideHomeAndRecents = false, + isKeyguardShowing = true, + isOccluded = true, + isPowerGestureIntercepted = true, + isAuthenticationMethodSecure = true, + isFaceEnrolledAndEnabled = false, + ), + ), + TestSpec( + id = 5, + expectedFlags = StatusBarManager.DISABLE_RECENT, + Preconditions( + isForceHideHomeAndRecents = false, + isKeyguardShowing = true, + isOccluded = true, + isPowerGestureIntercepted = true, + isAuthenticationMethodSecure = true, + isFaceEnrolledAndEnabled = true, + ), + ), + TestSpec( + id = 6, + expectedFlags = StatusBarManager.DISABLE_RECENT, + Preconditions( + isForceHideHomeAndRecents = true, + isShowHomeOverLockscreen = true, + isGesturalMode = true, + isPowerGestureIntercepted = false, + ), + ), + TestSpec( + id = 7, + expectedFlags = StatusBarManager.DISABLE_RECENT, + Preconditions( + isForceHideHomeAndRecents = false, + isKeyguardShowing = true, + isOccluded = false, + isShowHomeOverLockscreen = true, + isGesturalMode = true, + isPowerGestureIntercepted = false, + ), + ), + TestSpec( + id = 8, + expectedFlags = + StatusBarManager.DISABLE_RECENT or StatusBarManager.DISABLE_HOME, + Preconditions( + isForceHideHomeAndRecents = true, + isShowHomeOverLockscreen = true, + isGesturalMode = false, + isPowerGestureIntercepted = false, + ), + ), + TestSpec( + id = 9, + expectedFlags = + StatusBarManager.DISABLE_RECENT or StatusBarManager.DISABLE_HOME, + Preconditions( + isForceHideHomeAndRecents = false, + isKeyguardShowing = true, + isOccluded = false, + isShowHomeOverLockscreen = false, + isPowerGestureIntercepted = false, + ), + ), + ) + } + + @BeforeClass + @JvmStatic + fun setUpClass() { + val seenIds = mutableSetOf<Int>() + testSpecs().forEach { testSpec -> + assertWithMessage("Duplicate TestSpec id=${testSpec.id}") + .that(seenIds) + .doesNotContain(testSpec.id) + seenIds.add(testSpec.id) + } + } + } + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val statusBarServiceMock = kosmos.statusBarService + private val flagsCaptor = argumentCaptor<Int>() + + private val navigationModeControllerMock = kosmos.navigationModeController + private var currentNavigationMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON + set(value) { + field = value + modeChangedListeners.forEach { listener -> listener.onNavigationModeChanged(field) } + } + + private val modeChangedListeners = mutableListOf<NavigationModeController.ModeChangedListener>() + + private val underTest = kosmos.statusBarStartable + + @JvmField @Parameter(0) var testSpec: TestSpec? = null + + @Before + fun setUp() { + whenever(navigationModeControllerMock.addListener(any())).thenAnswer { invocation -> + val listener = invocation.arguments[0] as NavigationModeController.ModeChangedListener + modeChangedListeners.add(listener) + currentNavigationMode + } + + underTest.start() + } + + @Test + fun test() = + testScope.runTest { + val preconditions = checkNotNull(testSpec).preconditions + preconditions.assertValid() + + setUpWith(preconditions) + + runCurrent() + + verify(statusBarServiceMock, atLeastOnce()) + .disableForUser(flagsCaptor.capture(), any(), any(), anyInt()) + assertThat(flagsCaptor.lastValue).isEqualTo(checkNotNull(testSpec).expectedFlags) + } + + /** Sets up the state to match what's specified in the given [preconditions]. */ + private fun TestScope.setUpWith( + preconditions: Preconditions, + ) { + if (!preconditions.isKeyguardShowing) { + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + } + if (preconditions.isForceHideHomeAndRecents) { + whenIdle(Scenes.Bouncer) + } else if (preconditions.isKeyguardShowing) { + whenIdle(Scenes.Lockscreen) + } else { + whenIdle(Scenes.Gone) + } + runCurrent() + + kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + showWhenLockedActivityOnTop = preconditions.isOccluded, + taskInfo = if (preconditions.isOccluded) mock() else null, + ) + + kosmos.fakeDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, + preconditions.isShowHomeOverLockscreen.toString(), + /* makeDefault= */ false, + ) + kosmos.fakeExecutor.runAllReady() + + currentNavigationMode = + if (preconditions.isGesturalMode) { + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL + } else { + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON + } + + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + if (preconditions.isAuthenticationMethodSecure) { + AuthenticationMethodModel.Pin + } else { + AuthenticationMethodModel.None + } + ) + + kosmos.fakePowerRepository.updateWakefulness( + rawState = + if (preconditions.isPowerGestureIntercepted) WakefulnessState.AWAKE + else WakefulnessState.ASLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.POWER_BUTTON, + powerButtonLaunchGestureTriggered = preconditions.isPowerGestureIntercepted, + ) + + kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled( + preconditions.isFaceEnrolledAndEnabled + ) + + runCurrent() + } + + /** Sets up an idle state on the given [on] scene. */ + private fun whenIdle(on: SceneKey) { + kosmos.setSceneTransition(ObservableTransitionState.Idle(on)) + kosmos.sceneInteractor.changeScene(on, "") + } + + data class Preconditions( + val isForceHideHomeAndRecents: Boolean = false, + val isKeyguardShowing: Boolean = true, + val isOccluded: Boolean = false, + val isPowerGestureIntercepted: Boolean = false, + val isShowHomeOverLockscreen: Boolean = false, + val isGesturalMode: Boolean = true, + val isAuthenticationMethodSecure: Boolean = true, + val isFaceEnrolledAndEnabled: Boolean = false, + ) { + override fun toString(): String { + // Only include values set to true: + return buildString { + append("(") + append( + Preconditions::class + .memberProperties + .filter { it.get(this@Preconditions) == true } + .joinToString(", ") { "${it.name}=true" } + ) + append(")") + } + } + + fun assertValid() { + assertWithMessage( + "isForceHideHomeAndRecents means that the bouncer is showing so keyguard must" + + " be showing" + ) + .that(!isForceHideHomeAndRecents || isKeyguardShowing) + .isTrue() + assertWithMessage("Cannot be occluded if the keyguard isn't showing") + .that(!isOccluded || isKeyguardShowing) + .isTrue() + } + } + + data class TestSpec( + val id: Int, + val expectedFlags: Int, + val preconditions: Preconditions, + ) { + override fun toString(): String { + return "id=$id, expected=$expectedFlags, preconditions=$preconditions" + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt index ad4b98bdb3ee..eac86e55763a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt @@ -20,6 +20,7 @@ import android.app.smartspace.SmartspaceSession import android.app.smartspace.SmartspaceTarget import android.content.Context import android.graphics.drawable.Drawable +import android.os.Handler import android.testing.TestableLooper import android.view.View import android.widget.FrameLayout @@ -86,6 +87,8 @@ class CommunalSmartspaceControllerTest : SysuiTestCase() { override fun setUiSurface(uiSurface: String) {} + override fun setBgHandler(bgHandler: Handler?) {} + override fun setDozeAmount(amount: Float) {} override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt index 3a386311223f..e774aed2045b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt @@ -20,6 +20,7 @@ import android.app.smartspace.SmartspaceSession import android.app.smartspace.SmartspaceTarget import android.content.Context import android.graphics.drawable.Drawable +import android.os.Handler import android.testing.TestableLooper import android.view.View import android.view.ViewGroup @@ -119,6 +120,8 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { override fun setUiSurface(uiSurface: String) {} + override fun setBgHandler(bgHandler: Handler?) {} + override fun setDozeAmount(amount: Float) {} override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index ca4434d233a6..cc3fdc592b45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -32,6 +32,8 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationScrollViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos @@ -208,4 +210,50 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { assertThat(expandFraction).isEqualTo(1f) assertThat(isScrollable).isFalse() } + + @Test + fun shadeExpansion_goneToQs() = + testScope.runTest { + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(currentScene = Scenes.Gone) + ) + sceneInteractor.setTransitionState(transitionState) + val expandFraction by collectLastValue(scrollViewModel.expandFraction) + assertThat(expandFraction).isEqualTo(0f) + + fakeSceneDataSource.changeScene(toScene = Scenes.Gone) + val isScrollable by collectLastValue(scrollViewModel.isScrollable) + assertThat(isScrollable).isFalse() + + fakeSceneDataSource.pause() + + sceneInteractor.changeScene(Scenes.QuickSettings, "reason") + val transitionProgress = MutableStateFlow(0f) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Gone, + toScene = Scenes.QuickSettings, + currentScene = flowOf(Scenes.QuickSettings), + progress = transitionProgress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + val steps = 10 + repeat(steps) { repetition -> + val progress = (1f / steps) * (repetition + 1) + transitionProgress.value = progress + runCurrent() + assertThat(expandFraction) + .isEqualTo( + (progress / EXPANSION_FOR_MAX_SCRIM_ALPHA - + EXPANSION_FOR_DELAYED_STACK_FADE_IN) + .coerceIn(0f, 1f) + ) + } + + fakeSceneDataSource.unpause(expectedScene = Scenes.QuickSettings) + assertThat(expandFraction).isEqualTo(1f) + assertThat(isScrollable).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt index 8e765f7bb689..9ef42c30dcc1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt @@ -21,13 +21,13 @@ import android.media.AudioManager import android.provider.Settings.Global import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.statusbar.notification.data.model.ZenMode import com.android.settingslib.statusbar.notification.data.repository.updateNotificationPolicy import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.testKosmos import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat @@ -52,24 +52,20 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { @Before fun setup() { - with(kosmos) { - underTest = NotificationsSoundPolicyInteractor(notificationsSoundPolicyRepository) - } + with(kosmos) { underTest = NotificationsSoundPolicyInteractor(zenModeRepository) } } @Test fun onlyAlarmsCategory_areAlarmsAllowed_isTrue() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF)) + zenModeRepository.updateZenMode(Global.ZEN_MODE_OFF) val expectedByCategory = NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.associateWith { it == NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS } expectedByCategory.forEach { entry -> - notificationsSoundPolicyRepository.updateNotificationPolicy( - priorityCategories = entry.key - ) + zenModeRepository.updateNotificationPolicy(priorityCategories = entry.key) val areAlarmsAllowed by collectLastValue(underTest.areAlarmsAllowed) runCurrent() @@ -84,15 +80,13 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun onlyMediaCategory_areAlarmsAllowed_isTrue() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF)) + zenModeRepository.updateZenMode(Global.ZEN_MODE_OFF) val expectedByCategory = NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.associateWith { it == NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA } expectedByCategory.forEach { entry -> - notificationsSoundPolicyRepository.updateNotificationPolicy( - priorityCategories = entry.key - ) + zenModeRepository.updateNotificationPolicy(priorityCategories = entry.key) val isMediaAllowed by collectLastValue(underTest.isMediaAllowed) runCurrent() @@ -108,7 +102,7 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { with(kosmos) { testScope.runTest { for (category in NotificationManager.Policy.ALL_PRIORITY_CATEGORIES) { - notificationsSoundPolicyRepository.updateNotificationPolicy( + zenModeRepository.updateNotificationPolicy( priorityCategories = category, state = NotificationManager.Policy.STATE_UNSET, ) @@ -126,7 +120,7 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun allCategoriesAllowed_isRingerAllowed_isTrue() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateNotificationPolicy( + zenModeRepository.updateNotificationPolicy( priorityCategories = NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value -> acc or value @@ -146,7 +140,7 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun noCategoriesAndBlocked_isRingerAllowed_isFalse() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateNotificationPolicy( + zenModeRepository.updateNotificationPolicy( priorityCategories = 0, state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED, ) @@ -163,10 +157,8 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun zenModeNoInterruptions_allStreams_muted() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateNotificationPolicy() - notificationsSoundPolicyRepository.updateZenMode( - ZenMode(Global.ZEN_MODE_NO_INTERRUPTIONS) - ) + zenModeRepository.updateNotificationPolicy() + zenModeRepository.updateZenMode(Global.ZEN_MODE_NO_INTERRUPTIONS) for (stream in AudioStream.supportedStreamTypes) { val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream))) @@ -182,8 +174,8 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun zenModeOff_allStreams_notMuted() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateNotificationPolicy() - notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF)) + zenModeRepository.updateNotificationPolicy() + zenModeRepository.updateZenMode(Global.ZEN_MODE_OFF) for (stream in AudioStream.supportedStreamTypes) { val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream))) @@ -205,8 +197,8 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { AudioManager.STREAM_SYSTEM, ) testScope.runTest { - notificationsSoundPolicyRepository.updateNotificationPolicy() - notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_ALARMS)) + zenModeRepository.updateNotificationPolicy() + zenModeRepository.updateZenMode(Global.ZEN_MODE_ALARMS) for (stream in AudioStream.supportedStreamTypes) { val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream))) @@ -222,10 +214,8 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun alarms_allowed_notMuted() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateZenMode( - ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - ) - notificationsSoundPolicyRepository.updateNotificationPolicy( + zenModeRepository.updateZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + zenModeRepository.updateNotificationPolicy( priorityCategories = NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS ) @@ -242,10 +232,8 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun media_allowed_notMuted() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateZenMode( - ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - ) - notificationsSoundPolicyRepository.updateNotificationPolicy( + zenModeRepository.updateZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + zenModeRepository.updateNotificationPolicy( priorityCategories = NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA ) @@ -262,10 +250,8 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun ringer_allowed_notificationsNotMuted() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateZenMode( - ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - ) - notificationsSoundPolicyRepository.updateNotificationPolicy( + zenModeRepository.updateZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + zenModeRepository.updateNotificationPolicy( priorityCategories = NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value -> acc or value @@ -288,10 +274,8 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { fun ringer_allowed_ringNotMuted() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateZenMode( - ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - ) - notificationsSoundPolicyRepository.updateNotificationPolicy( + zenModeRepository.updateZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + zenModeRepository.updateNotificationPolicy( priorityCategories = NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value -> acc or value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt new file mode 100644 index 000000000000..5e87f4663d76 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.row.ui.viewmodel + +import android.app.PendingIntent +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository +import com.android.systemui.statusbar.notification.row.shared.IconModel +import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag +import com.android.systemui.statusbar.notification.row.shared.TimerContentModel +import com.android.systemui.statusbar.notification.row.shared.TimerContentModel.TimerState.Paused +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import java.time.Duration +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +@SmallTest +@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME) +class TimerViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val repository = kosmos.fakeNotificationRowRepository + + private var contentModel: TimerContentModel? + get() = repository.richOngoingContentModel.value as? TimerContentModel + set(value) { + repository.richOngoingContentModel.value = value + } + + private lateinit var underTest: TimerViewModel + + @Before + fun setup() { + underTest = kosmos.getTimerViewModel(repository) + } + + @Test + fun labelShowsTheTimerName() = + testScope.runTest { + val label by collectLastValue(underTest.label) + contentModel = pausedTimer(name = "Example Timer Name") + assertThat(label).isEqualTo("Example Timer Name") + } + + @Test + fun pausedTimeRemainingFormatsWell() = + testScope.runTest { + val label by collectLastValue(underTest.pausedTime) + contentModel = pausedTimer(timeRemaining = Duration.ofMinutes(3)) + assertThat(label).isEqualTo("3:00") + contentModel = pausedTimer(timeRemaining = Duration.ofSeconds(119)) + assertThat(label).isEqualTo("1:59") + contentModel = pausedTimer(timeRemaining = Duration.ofSeconds(121)) + assertThat(label).isEqualTo("2:01") + contentModel = pausedTimer(timeRemaining = Duration.ofHours(1)) + assertThat(label).isEqualTo("1:00:00") + contentModel = pausedTimer(timeRemaining = Duration.ofHours(24)) + assertThat(label).isEqualTo("24:00:00") + } + + private fun pausedTimer( + icon: IconModel = mock(), + name: String = "example", + timeRemaining: Duration = Duration.ofMinutes(3), + resumeIntent: PendingIntent? = null, + resetIntent: PendingIntent? = null + ) = + TimerContentModel( + icon = icon, + name = name, + state = + Paused( + timeRemaining = timeRemaining, + resumeIntent = resumeIntent, + resetIntent = resetIntent, + ) + ) +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index 9fde116e968c..40315a27da4f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -23,6 +23,7 @@ import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import androidx.test.filters.SmallTest +import com.android.settingslib.statusbar.notification.data.repository.updateNotificationPolicy import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags @@ -56,7 +57,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -97,7 +97,9 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Before fun setUp() { - MockitoAnnotations.initMocks(this) + // "Why is this not lazily initialised above?" you may ask. There's a simple answer: likely + // due to some timing issue with how the flags are getting initialised for parameterization, + // some tests start failing when this isn't initialised this way. You can just leave it be. underTest = kosmos.notificationListViewModel } @@ -146,36 +148,40 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas assertThat(important).isTrue() } + // NOTE: The empty shade view and the footer view should be mutually exclusive. + @Test - fun shouldIncludeEmptyShadeView_trueWhenNoNotifs() = + fun shouldShowEmptyShadeView_trueWhenNoNotifs() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) runCurrent() // THEN empty shade is visible - assertThat(shouldInclude).isTrue() + assertThat(shouldShowEmptyShadeView).isTrue() + assertThat(shouldIncludeFooterView?.value).isFalse() } @Test - fun shouldIncludeEmptyShadeView_falseWhenNotifs() = + fun shouldShowEmptyShadeView_falseWhenNotifs() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) runCurrent() // THEN empty shade is not visible - assertThat(shouldInclude).isFalse() + assertThat(shouldShowEmptyShadeView).isFalse() } @Test - fun shouldIncludeEmptyShadeView_falseWhenQsExpandedDefault() = + fun shouldShowEmptyShadeView_falseWhenQsExpandedDefault() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -184,13 +190,14 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas runCurrent() // THEN empty shade is not visible - assertThat(shouldInclude).isFalse() + assertThat(shouldShow).isFalse() } @Test - fun shouldIncludeEmptyShadeView_trueWhenQsExpandedInSplitShade() = + fun shouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -203,13 +210,15 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas runCurrent() // THEN empty shade is visible - assertThat(shouldInclude).isTrue() + assertThat(shouldShowEmptyShadeView).isTrue() + assertThat(shouldIncludeFooterView?.value).isFalse() } @Test - fun shouldIncludeEmptyShadeView_trueWhenLockedShade() = + fun shouldShowEmptyShadeView_trueWhenLockedShade() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -218,13 +227,14 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas runCurrent() // THEN empty shade is visible - assertThat(shouldInclude).isTrue() + assertThat(shouldShowEmptyShadeView).isTrue() + assertThat(shouldIncludeFooterView?.value).isFalse() } @Test - fun shouldIncludeEmptyShadeView_falseWhenKeyguard() = + fun shouldShowEmptyShadeView_falseWhenKeyguard() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -233,13 +243,13 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas runCurrent() // THEN empty shade is not visible - assertThat(shouldInclude).isFalse() + assertThat(shouldShow).isFalse() } @Test - fun shouldIncludeEmptyShadeView_falseWhenStartingToSleep() = + fun shouldShowEmptyShadeView_falseWhenStartingToSleep() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -250,7 +260,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas runCurrent() // THEN empty shade is not visible - assertThat(shouldInclude).isFalse() + assertThat(shouldShow).isFalse() } @Test @@ -258,8 +268,10 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas testScope.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) - zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + zenModeRepository.updateNotificationPolicy( + suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST + ) + zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) runCurrent() assertThat(hidden).isTrue() @@ -270,8 +282,10 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas testScope.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) - zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_OFF + zenModeRepository.updateNotificationPolicy( + suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST + ) + zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF) runCurrent() assertThat(hidden).isFalse() @@ -302,7 +316,8 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_trueWhenShade() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) + val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) + val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -312,13 +327,15 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas runCurrent() // THEN footer is visible - assertThat(shouldInclude?.value).isTrue() + assertThat(shouldIncludeFooterView?.value).isTrue() + assertThat(shouldShowEmptyShadeView).isFalse() } @Test fun shouldIncludeFooterView_trueWhenLockedShade() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) + val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) + val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -328,7 +345,8 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas runCurrent() // THEN footer is visible - assertThat(shouldInclude?.value).isTrue() + assertThat(shouldIncludeFooterView?.value).isTrue() + assertThat(shouldShowEmptyShadeView).isFalse() } @Test @@ -404,7 +422,8 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_trueWhenQsExpandedSplitShade() = testScope.runTest { - val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) + val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) + val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -419,7 +438,8 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas runCurrent() // THEN footer is visible - assertThat(shouldInclude?.value).isTrue() + assertThat(shouldIncludeFooterView?.value).isTrue() + assertThat(shouldShowEmptyShadeView).isFalse() } @Test @@ -528,9 +548,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas FakeHeadsUpRowRepository(key = "1"), FakeHeadsUpRowRepository(key = "2"), ) - headsUpRepository.setNotifications( - rows, - ) + headsUpRepository.setNotifications(rows) runCurrent() // THEN the list is empty @@ -566,7 +584,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas headsUpRepository.setNotifications( FakeHeadsUpRowRepository(key = "0", isPinned = true), - FakeHeadsUpRowRepository(key = "1") + FakeHeadsUpRowRepository(key = "1"), ) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 497484f90ca9..f14c96ded873 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -23,11 +23,15 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.BrokenWithSceneContainer @@ -134,6 +138,9 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S val largeScreenHeaderHelper get() = kosmos.mockLargeScreenHeaderHelper + val communalSceneRepository + get() = kosmos.communalSceneRepository + lateinit var underTest: SharedNotificationContainerViewModel @Before @@ -845,26 +852,24 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S @Test @DisableSceneContainer fun updateBounds_fromGone_withoutTransitions() = - testScope.runTest { - // Start step is already at 1.0 - val runningStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.RUNNING) - val finishStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.FINISHED) - - val bounds by collectLastValue(underTest.bounds) - val top = 123f - val bottom = 456f - - kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep) - runCurrent() - kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep) - runCurrent() - keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) - runCurrent() - - assertThat(bounds).isEqualTo( - NotificationContainerBounds(top = top, bottom = bottom) - ) - } + testScope.runTest { + // Start step is already at 1.0 + val runningStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.RUNNING) + val finishStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.FINISHED) + + val bounds by collectLastValue(underTest.bounds) + val top = 123f + val bottom = 456f + + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep) + runCurrent() + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep) + runCurrent() + keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) + runCurrent() + + assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) + } @Test fun alphaOnFullQsExpansion() = @@ -1020,6 +1025,230 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S assertThat(fadeIn[0]).isEqualTo(false) } + @Test + @BrokenWithSceneContainer(330311871) + fun alpha_isZero_fromPrimaryBouncerToGoneWhileCommunalSceneVisible() = + testScope.runTest { + val viewState = ViewStateAccessor() + val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) + + showPrimaryBouncer() + showCommunalScene() + + // PRIMARY_BOUNCER->GONE transition is started + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = GONE, + transitionState = TransitionState.STARTED, + value = 0f, + ) + ) + runCurrent() + + // PRIMARY_BOUNCER->GONE transition running + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = GONE, + transitionState = TransitionState.RUNNING, + value = 0.1f, + ) + ) + runCurrent() + assertThat(alpha).isEqualTo(0f) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = GONE, + transitionState = TransitionState.RUNNING, + value = 0.9f, + ) + ) + runCurrent() + assertThat(alpha).isEqualTo(0f) + + hideCommunalScene() + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = GONE, + transitionState = TransitionState.FINISHED, + value = 1f + ) + ) + runCurrent() + assertThat(alpha).isEqualTo(0f) + } + + @Test + @BrokenWithSceneContainer(330311871) + fun alpha_fromPrimaryBouncerToGoneWhenCommunalSceneNotVisible() = + testScope.runTest { + val viewState = ViewStateAccessor() + val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) + + showPrimaryBouncer() + hideCommunalScene() + + // PRIMARY_BOUNCER->GONE transition is started + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = GONE, + transitionState = TransitionState.STARTED, + ) + ) + runCurrent() + + // PRIMARY_BOUNCER->GONE transition running + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = GONE, + transitionState = TransitionState.RUNNING, + value = 0.1f, + ) + ) + runCurrent() + assertThat(alpha).isIn(Range.closedOpen(0f, 1f)) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = GONE, + transitionState = TransitionState.RUNNING, + value = 0.9f, + ) + ) + runCurrent() + assertThat(alpha).isIn(Range.closedOpen(0f, 1f)) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = GONE, + transitionState = TransitionState.FINISHED, + value = 1f + ) + ) + runCurrent() + assertThat(alpha).isEqualTo(0f) + } + + @Test + @BrokenWithSceneContainer(330311871) + fun alpha_isZero_fromAlternateBouncerToGoneWhileCommunalSceneVisible() = + testScope.runTest { + val viewState = ViewStateAccessor() + val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) + + showAlternateBouncer() + showCommunalScene() + + // ALTERNATE_BOUNCER->GONE transition is started + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = GONE, + transitionState = TransitionState.STARTED, + value = 0f, + ) + ) + runCurrent() + + // ALTERNATE_BOUNCER->GONE transition running + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = GONE, + transitionState = TransitionState.RUNNING, + value = 0.1f, + ) + ) + runCurrent() + assertThat(alpha).isEqualTo(0f) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = GONE, + transitionState = TransitionState.RUNNING, + value = 0.9f, + ) + ) + runCurrent() + assertThat(alpha).isEqualTo(0f) + + hideCommunalScene() + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = GONE, + transitionState = TransitionState.FINISHED, + value = 1f + ) + ) + runCurrent() + assertThat(alpha).isEqualTo(0f) + } + + @Test + @BrokenWithSceneContainer(330311871) + fun alpha_fromAlternateBouncerToGoneWhenCommunalSceneNotVisible() = + testScope.runTest { + val viewState = ViewStateAccessor() + val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) + + showAlternateBouncer() + hideCommunalScene() + + // ALTERNATE_BOUNCER->GONE transition is started + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = GONE, + transitionState = TransitionState.STARTED, + ) + ) + runCurrent() + + // ALTERNATE_BOUNCER->GONE transition running + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = GONE, + transitionState = TransitionState.RUNNING, + value = 0.1f, + ) + ) + runCurrent() + assertThat(alpha).isIn(Range.closedOpen(0f, 1f)) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = GONE, + transitionState = TransitionState.RUNNING, + value = 0.9f, + ) + ) + runCurrent() + assertThat(alpha).isIn(Range.closedOpen(0f, 1f)) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = GONE, + transitionState = TransitionState.FINISHED, + value = 1f + ) + ) + runCurrent() + assertThat(alpha).isEqualTo(0f) + } + private suspend fun TestScope.showLockscreen() { shadeTestUtil.setQsExpansion(0f) shadeTestUtil.setLockscreenShadeExpansion(0f) @@ -1071,4 +1300,52 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S testScope, ) } + + private suspend fun TestScope.showPrimaryBouncer() { + shadeTestUtil.setQsExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) + runCurrent() + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + runCurrent() + kosmos.keyguardBouncerRepository.setPrimaryShow(true) + runCurrent() + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.PRIMARY_BOUNCER, + testScope, + ) + } + + private suspend fun TestScope.showAlternateBouncer() { + shadeTestUtil.setQsExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) + runCurrent() + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + runCurrent() + kosmos.keyguardBouncerRepository.setPrimaryShow(false) + runCurrent() + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.ALTERNATE_BOUNCER, + testScope, + ) + } + + private fun TestScope.showCommunalScene() { + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) + ) + communalSceneRepository.setTransitionState(transitionState) + runCurrent() + } + + private fun TestScope.hideCommunalScene() { + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Blank) + ) + communalSceneRepository.setTransitionState(transitionState) + runCurrent() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt index 57d325129fa2..1656a2e680da 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt @@ -60,6 +60,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -132,6 +133,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { communalSceneInteractor = communalSceneInteractor, ) whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER) + whenever(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false)) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt new file mode 100644 index 000000000000..f1fed1904bc5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.domain.interactor + +import android.app.NotificationManager.Policy +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.statusbar.notification.data.repository.updateNotificationPolicy +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +@SmallTest +class ZenModeInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val repository = kosmos.fakeZenModeRepository + + private val underTest = kosmos.zenModeInteractor + + @Test + fun isZenModeEnabled_off() = + testScope.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.updateZenMode(Settings.Global.ZEN_MODE_OFF) + runCurrent() + + assertThat(enabled).isFalse() + } + + @Test + fun isZenModeEnabled_alarms() = + testScope.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.updateZenMode(Settings.Global.ZEN_MODE_ALARMS) + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun isZenModeEnabled_importantInterruptions() = + testScope.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun isZenModeEnabled_noInterruptions() = + testScope.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun testIsZenModeEnabled_unknown() = + testScope.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.updateZenMode(4) // this should fail if we ever add another zen mode type + runCurrent() + + assertThat(enabled).isFalse() + } + + @Test + fun areNotificationsHiddenInShade_noPolicy() = + testScope.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.updateNotificationPolicy(null) + repository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun areNotificationsHiddenInShade_zenOffShadeSuppressed() = + testScope.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.updateNotificationPolicy( + suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST + ) + repository.updateZenMode(Settings.Global.ZEN_MODE_OFF) + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun areNotificationsHiddenInShade_zenOnShadeNotSuppressed() = + testScope.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.updateNotificationPolicy( + suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_STATUS_BAR + ) + repository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun areNotificationsHiddenInShade_zenOnShadeSuppressed() = + testScope.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.updateNotificationPolicy( + suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST + ) + repository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + runCurrent() + + assertThat(hidden).isTrue() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt index d620639b2219..6e49e431bd80 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt @@ -20,7 +20,6 @@ import android.media.AudioManager import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.statusbar.notification.data.model.ZenMode import com.android.settingslib.statusbar.notification.data.repository.updateNotificationPolicy import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor import com.android.settingslib.volume.shared.model.AudioStream @@ -29,7 +28,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor -import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyRepository +import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.testKosmos import com.android.systemui.volume.data.repository.audioRepository import com.google.common.truth.Truth.assertThat @@ -104,10 +103,8 @@ class AudioVolumeInteractorTest : SysuiTestCase() { fun zenMuted_cantChange() { with(kosmos) { testScope.runTest { - notificationsSoundPolicyRepository.updateNotificationPolicy() - notificationsSoundPolicyRepository.updateZenMode( - ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) - ) + zenModeRepository.updateNotificationPolicy() + zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) val canChangeVolume by collectLastValue( @@ -141,9 +138,7 @@ class AudioVolumeInteractorTest : SysuiTestCase() { with(kosmos) { testScope.runTest { audioRepository.setLastAudibleVolume(audioStream, 30) - notificationsSoundPolicyRepository.updateZenMode( - ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) - ) + zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) val model by collectLastValue(underTest.getAudioStream(audioStream)) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt index 777240c57c2e..5826b3feae37 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt @@ -17,8 +17,10 @@ package com.android.systemui.volume.panel.component.spatial import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.kosmos.testScope import com.android.systemui.media.spatializerInteractor +import com.android.systemui.volume.data.repository.audioRepository import com.android.systemui.volume.domain.interactor.audioOutputInteractor import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor @@ -27,6 +29,8 @@ val Kosmos.spatialAudioComponentInteractor by SpatialAudioComponentInteractor( audioOutputInteractor, spatializerInteractor, + audioRepository, + backgroundCoroutineContext, testScope.backgroundScope ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt index 2f69942aa459..ebc78d864494 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.volume.panel.component.spatial.domain +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothProfile import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo import android.media.session.MediaSession @@ -24,12 +26,14 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LeAudioProfile import com.android.settingslib.media.BluetoothMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.media.spatializerRepository import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.volume.localMediaController @@ -56,8 +60,15 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { @Before fun setup() { with(kosmos) { + val leAudioProfile = + mock<LeAudioProfile> { + whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO) + whenever(isEnabled(any())).thenReturn(true) + } val cachedBluetoothDevice: CachedBluetoothDevice = mock { whenever(address).thenReturn("test_address") + whenever(profiles).thenReturn(listOf(leAudioProfile)) + whenever(device).thenReturn(mock<BluetoothDevice> {}) } localMediaRepository.updateCurrentConnectedDevice( mock<BluetoothMediaDevice> { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt index c6c46faf97f7..d5566adb1fdc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt @@ -16,14 +16,22 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothProfile import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo import android.media.session.MediaSession import android.media.session.PlaybackState +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.settingslib.bluetooth.A2dpProfile import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.HearingAidProfile +import com.android.settingslib.bluetooth.LeAudioProfile +import com.android.settingslib.flags.Flags import com.android.settingslib.media.BluetoothMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -44,6 +52,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -52,15 +61,33 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class SpatialAudioComponentInteractorTest : SysuiTestCase() { + @get:Rule val setFlagsRule = SetFlagsRule() private val kosmos = testKosmos() private lateinit var underTest: SpatialAudioComponentInteractor + private val bluetoothDevice: BluetoothDevice = mock {} + private val a2dpProfile: A2dpProfile = mock { + whenever(profileId).thenReturn(BluetoothProfile.A2DP) + whenever(isEnabled(bluetoothDevice)).thenReturn(false) + } + private val leAudioProfile: LeAudioProfile = mock { + whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO) + whenever(isEnabled(bluetoothDevice)).thenReturn(true) + } + private val hearingAidProfile: HearingAidProfile = mock { + whenever(profileId).thenReturn(BluetoothProfile.HEARING_AID) + whenever(isEnabled(bluetoothDevice)).thenReturn(false) + } + @Before fun setup() { with(kosmos) { val cachedBluetoothDevice: CachedBluetoothDevice = mock { whenever(address).thenReturn("test_address") + whenever(device).thenReturn(bluetoothDevice) + whenever(profiles) + .thenReturn(listOf(a2dpProfile, leAudioProfile, hearingAidProfile)) } localMediaRepository.updateCurrentConnectedDevice( mock<BluetoothMediaDevice> { @@ -83,7 +110,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { fun setEnabled_changesIsEnabled() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsSpatialAudioAvailable(headset, true) + spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true) val values by collectValues(underTest.isEnabled) underTest.setEnabled(SpatialAudioEnabledModel.Disabled) @@ -106,10 +133,39 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DETERMINING_SPATIAL_AUDIO_ATTRIBUTES_BY_PROFILE) + fun setEnabled_determinedByBluetoothProfile_a2dpProfileEnabled() { + with(kosmos) { + testScope.runTest { + whenever(a2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true) + whenever(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(false) + whenever(hearingAidProfile.isEnabled(bluetoothDevice)).thenReturn(false) + spatializerRepository.setIsSpatialAudioAvailable(a2dpAttributes, true) + val values by collectValues(underTest.isEnabled) + + underTest.setEnabled(SpatialAudioEnabledModel.Disabled) + runCurrent() + underTest.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled) + runCurrent() + + assertThat(values) + .containsExactly( + SpatialAudioEnabledModel.Unknown, + SpatialAudioEnabledModel.Disabled, + SpatialAudioEnabledModel.SpatialAudioEnabled, + ) + .inOrder() + assertThat(spatializerRepository.getSpatialAudioCompatibleDevices()) + .containsExactly(a2dpAttributes) + } + } + } + + @Test fun connectedDeviceSupports_isAvailable_SpatialAudio() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsSpatialAudioAvailable(headset, true) + spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true) val isAvailable by collectLastValue(underTest.isAvailable) @@ -123,8 +179,8 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsSpatialAudioAvailable(headset, true) - spatializerRepository.setIsHeadTrackingAvailable(headset, true) + spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true) + spatializerRepository.setIsHeadTrackingAvailable(bleHeadsetAttributes, true) val isAvailable by collectLastValue(underTest.isAvailable) @@ -138,7 +194,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { fun connectedDeviceDoesntSupport_isAvailable_Unavailable() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsSpatialAudioAvailable(headset, false) + spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, false) val isAvailable by collectLastValue(underTest.isAvailable) @@ -179,7 +235,13 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { } private companion object { - val headset = + val a2dpAttributes = + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + "test_address" + ) + val bleHeadsetAttributes = AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLE_HEADSET, diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index 9ad4012cfd07..074277c55c88 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -24,6 +24,7 @@ import android.app.smartspace.uitemplatedata.TapAction; import android.content.ActivityNotFoundException; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.os.Parcelable; import android.util.Log; import android.view.View; @@ -123,6 +124,9 @@ public interface BcSmartspaceDataPlugin extends Plugin { */ void setUiSurface(String uiSurface); + /** Set background handler to make binder calls. */ + void setBgHandler(Handler bgHandler); + /** * Range [0.0 - 1.0] when transitioning from Lockscreen to/from AOD */ diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml index cf9ca157b943..c9850f2613b1 100644 --- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml +++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml @@ -19,8 +19,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sysui="http://schemas.android.com/apk/res-auto" android:id="@+id/alternate_bouncer" - android:focusable="true" - android:clickable="true" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/drawable/ic_check_box.xml b/packages/SystemUI/res/drawable/ic_check_box.xml deleted file mode 100644 index a8d1a652b35b..000000000000 --- a/packages/SystemUI/res/drawable/ic_check_box.xml +++ /dev/null @@ -1,26 +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 - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:id="@+id/checked" - android:state_checked="true" - android:drawable="@drawable/ic_check_box_blue_24dp" /> - <item - android:id="@+id/unchecked" - android:state_checked="false" - android:drawable="@drawable/ic_check_box_outline_24dp" /> -</selector> diff --git a/packages/SystemUI/res/drawable/ic_check_box_blue_24dp.xml b/packages/SystemUI/res/drawable/ic_check_box_blue_24dp.xml deleted file mode 100644 index 43cae6983981..000000000000 --- a/packages/SystemUI/res/drawable/ic_check_box_blue_24dp.xml +++ /dev/null @@ -1,26 +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 - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" - android:fillColor="#4285F4"/> -</vector> - diff --git a/packages/SystemUI/res/drawable/ic_check_box_outline_24dp.xml b/packages/SystemUI/res/drawable/ic_check_box_outline_24dp.xml deleted file mode 100644 index f6f453af2a26..000000000000 --- a/packages/SystemUI/res/drawable/ic_check_box_outline_24dp.xml +++ /dev/null @@ -1,26 +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 - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z" - android:fillColor="#757575"/> -</vector> - diff --git a/packages/SystemUI/res/drawable/ic_speaker_group_black_24dp.xml b/packages/SystemUI/res/drawable/ic_speaker_group_black_24dp.xml deleted file mode 100644 index ae0d56217fd9..000000000000 --- a/packages/SystemUI/res/drawable/ic_speaker_group_black_24dp.xml +++ /dev/null @@ -1,31 +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 - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z" - android:fillColor="#000000"/> - <path - android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0" - android:fillColor="#000000"/> - <path - android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z" - android:fillColor="#000000"/> -</vector> diff --git a/packages/SystemUI/res/drawable/placeholder_touchpad_back_gesture.png b/packages/SystemUI/res/drawable/placeholder_touchpad_back_gesture.png Binary files differnew file mode 100644 index 000000000000..526b5850b2d1 --- /dev/null +++ b/packages/SystemUI/res/drawable/placeholder_touchpad_back_gesture.png diff --git a/packages/SystemUI/res/drawable/placeholder_touchpad_tablet_back_gesture.png b/packages/SystemUI/res/drawable/placeholder_touchpad_tablet_back_gesture.png Binary files differnew file mode 100644 index 000000000000..cba2d2036261 --- /dev/null +++ b/packages/SystemUI/res/drawable/placeholder_touchpad_tablet_back_gesture.png diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml index bcc7bca8c915..a3af9490f585 100644 --- a/packages/SystemUI/res/layout/app_clips_screenshot.xml +++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml @@ -51,6 +51,15 @@ app:layout_constraintStart_toEndOf="@id/save" app:layout_constraintTop_toTopOf="parent" /> + <TextView + android:id="@+id/backlinks_data" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:visibility="gone" + app:layout_constraintStart_toEndOf="@id/cancel" + app:layout_constraintTop_toTopOf="parent" /> + <ImageView android:id="@+id/preview" android:layout_width="0px" diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml deleted file mode 100644 index 5b1ec7f59b55..000000000000 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ /dev/null @@ -1,146 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/device_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - <FrameLayout - android:layout_width="match_parent" - android:layout_height="64dp" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" - android:layout_marginBottom="12dp"> - <FrameLayout - android:id="@+id/item_layout" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/media_output_item_background" - android:layout_gravity="center_vertical|start"> - <com.android.systemui.media.dialog.MediaOutputSeekbar - android:id="@+id/volume_seekbar" - android:splitTrack="false" - android:visibility="gone" - android:paddingStart="0dp" - android:paddingEnd="0dp" - android:background="@null" - android:contentDescription="@string/media_output_dialog_accessibility_seekbar" - android:progressDrawable="@drawable/media_output_dialog_seekbar_background" - android:thumb="@null" - android:layout_width="match_parent" - android:layout_height="match_parent"/> - </FrameLayout> - - <FrameLayout - android:layout_width="56dp" - android:layout_height="64dp" - android:layout_gravity="center_vertical|start"> - <ImageView - android:id="@+id/title_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_gravity="center"/> - </FrameLayout> - - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="56dp" - android:layout_marginEnd="56dp" - android:ellipsize="end" - android:maxLines="1" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:textSize="16sp"/> - - <LinearLayout - android:id="@+id/two_line_layout" - android:orientation="vertical" - android:layout_width="wrap_content" - android:layout_gravity="center_vertical|start" - android:layout_height="48dp" - android:layout_marginEnd="56dp" - android:layout_marginStart="56dp"> - <TextView - android:id="@+id/two_line_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="end" - android:maxLines="1" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:textColor="@color/media_dialog_item_main_content" - android:textSize="16sp"/> - <TextView - android:id="@+id/subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="end" - android:maxLines="1" - android:textColor="@color/media_dialog_item_main_content" - android:textSize="14sp" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:visibility="gone"/> - </LinearLayout> - - <ProgressBar - android:id="@+id/volume_indeterminate_progress" - style="?android:attr/progressBarStyleSmallTitle" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginEnd="16dp" - android:indeterminate="true" - android:layout_gravity="end|center" - android:indeterminateOnly="true" - android:visibility="gone"/> - - <ImageView - android:id="@+id/media_output_item_status" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginEnd="16dp" - android:indeterminate="true" - android:layout_gravity="end|center" - android:indeterminateOnly="true" - android:importantForAccessibility="no" - android:visibility="gone"/> - - <LinearLayout - android:id="@+id/end_action_area" - android:visibility="gone" - android:orientation="vertical" - android:layout_width="48dp" - android:layout_height="64dp" - android:layout_gravity="end|center" - android:gravity="center_vertical"> - <CheckBox - android:id="@+id/check_box" - android:focusable="false" - android:importantForAccessibility="no" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginEnd="16dp" - android:layout_gravity="end" - android:button="@drawable/ic_circle_check_box" - android:visibility="gone" - /> - - </LinearLayout> - </FrameLayout> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml index cd5c37d43633..beb16b38e6e0 100644 --- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml @@ -46,6 +46,8 @@ android:tint="?android:attr/colorPrimary" /> + <!-- Only one of [ongoing_activity_chip_time, ongoing_activity_chip_text] will ever + be shown at one time. --> <com.android.systemui.statusbar.chips.ui.view.ChipChronometer android:id="@+id/ongoing_activity_chip_time" android:layout_width="wrap_content" @@ -58,5 +60,19 @@ android:textColor="?android:attr/colorPrimary" /> + <!-- Used to show generic text in the chip instead of a timer. --> + <TextView + android:id="@+id/ongoing_activity_chip_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:gravity="center|start" + android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding" + android:textAppearance="@android:style/TextAppearance.Material.Small" + android:fontFamily="@*android:string/config_headlineFontFamily" + android:textColor="?android:attr/colorPrimary" + android:visibility="gone" + /> + </com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer> </FrameLayout> diff --git a/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml new file mode 100644 index 000000000000..f2bfbe5c960d --- /dev/null +++ b/packages/SystemUI/res/layout/rich_ongoing_timer_notification.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.systemui.statusbar.notification.row.ui.view.TimerView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/topBaseline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_begin="22sp" + /> + + <ImageView + android:id="@+id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:src="@drawable/ic_close" + app:tint="@android:color/white" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/label" + android:baseline="18dp" + app:layout_constraintBaseline_toTopOf="@id/topBaseline" + /> + <TextView + android:id="@+id/label" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintStart_toEndOf="@id/icon" + app:layout_constraintEnd_toStartOf="@id/chronoRemaining" + android:singleLine="true" + tools:text="15s Timer" + app:layout_constraintBaseline_toTopOf="@id/topBaseline" + android:paddingEnd="4dp" + /> + <Chronometer + android:id="@+id/chronoRemaining" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:singleLine="true" + android:textSize="20sp" + android:gravity="end" + tools:text="0:12" + app:layout_constraintBaseline_toTopOf="@id/topBaseline" + app:layout_constraintEnd_toStartOf="@id/pausedTimeRemaining" + app:layout_constraintStart_toEndOf="@id/label" + android:countDown="true" + android:paddingEnd="4dp" + /> + <TextView + android:id="@+id/pausedTimeRemaining" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:singleLine="true" + android:textSize="20sp" + android:gravity="end" + tools:text="0:12" + app:layout_constraintBaseline_toTopOf="@id/topBaseline" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/chronoRemaining" + android:paddingEnd="4dp" + /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/bottomOfTop" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:barrierDirection="bottom" + app:constraint_referenced_ids="icon,label,chronoRemaining,pausedTimeRemaining" + /> + + <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView + android:id="@+id/mainButton" + android:layout_width="124dp" + android:layout_height="wrap_content" + tools:text="Reset" + tools:drawableStart="@android:drawable/ic_menu_add" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/altButton" + app:layout_constraintTop_toBottomOf="@id/bottomOfTop" + app:layout_constraintHorizontal_chainStyle="spread" + android:paddingEnd="4dp" + /> + + <com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView + android:id="@+id/altButton" + tools:text="Reset" + tools:drawableStart="@android:drawable/ic_menu_add" + android:drawablePadding="2dp" + android:drawableTint="@android:color/white" + android:layout_width="124dp" + android:layout_height="wrap_content" + app:layout_constraintTop_toBottomOf="@id/bottomOfTop" + app:layout_constraintStart_toEndOf="@id/mainButton" + app:layout_constraintEnd_toEndOf="parent" + android:paddingEnd="4dp" + /> +</com.android.systemui.statusbar.notification.row.ui.view.TimerView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml index 84ab0f1b6ee5..fff1de7c1049 100644 --- a/packages/SystemUI/res/layout/screenshot_shelf.xml +++ b/packages/SystemUI/res/layout/screenshot_shelf.xml @@ -51,7 +51,6 @@ android:layout_marginBottom="@dimen/overlay_border_width" android:layout_gravity="center" android:elevation="4dp" - android:contentDescription="@string/screenshot_edit_description" android:scaleType="fitEnd" android:background="@drawable/overlay_preview_background" android:adjustViewBounds="true" @@ -67,7 +66,6 @@ android:layout_marginBottom="@dimen/overlay_border_width" android:layout_gravity="center" android:elevation="4dp" - android:contentDescription="@string/screenshot_edit_description" android:scaleType="fitEnd" android:background="@drawable/overlay_preview_background" android:adjustViewBounds="true" diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml index fc4bf8a65643..e80ed26cffe5 100644 --- a/packages/SystemUI/res/layout/sidefps_view.xml +++ b/packages/SystemUI/res/layout/sidefps_view.xml @@ -22,5 +22,4 @@ android:layout_height="wrap_content" app:lottie_autoPlay="true" app:lottie_loop="true" - app:lottie_rawRes="@raw/sfps_pulse" - android:importantForAccessibility="no"/>
\ No newline at end of file + app:lottie_rawRes="@raw/sfps_pulse"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index c7753364c155..fd768d8a95c9 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tik vir meer inligting"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Geen wekker nie"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"voer skermslot in"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Vingerafdruksensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"staaf"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"gaan by toestel in"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index de4138fad93f..9c24d3072210 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1195,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ለበለጠ መረጃ መታ ያድርጉ"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ምንም ማንቂያ አልተቀናበረም"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ማያ ገጽ መቆለፊያ ያስገቡ"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"የጣት አሻራ ዳሳሽ"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ያረጋግጡ"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"መሣሪያን ያስገቡ"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 8bd52d6cc7c8..8711e7803837 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"انقر للحصول على مزيد من المعلومات."</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"لم يتم ضبط منبّه"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"إدخال الرمز لفتح القفل"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"مستشعر بصمات الإصبع"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"المصادقة"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"الدخول إلى الجهاز"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 77774ccd5123..785f3f497a24 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Quick Share আৰু Find My Deviceৰ দৰে সুবিধাসমূহে ব্লুটুথ ব্যৱহাৰ কৰে"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"কাইলৈ পুৱা ব্লুটুথ অন হ’ব"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"অডিঅ’ শ্বেয়াৰ কৰক"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"অডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"বেটাৰী <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিঅ’"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডছেট"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"অধিক তথ্যৰ বাবে টিপক"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"কোনো এলাৰ্ম ছেট কৰা হোৱা নাই"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"স্ক্ৰীন লকটো দিয়ক"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰ"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"বিশ্বাসযোগ্যতা প্ৰমাণ কৰক"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ডিভাইচ আনলক কৰক"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index bf24c075a6dc..1c75a9b3d108 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ətraflı məlumat üçün toxunun"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Siqnal ayarlanmayıb"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran kilidi daxil edin"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Barmaq izi sensoru"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"doğrulayın"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"cihaz daxil edin"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 02271f0b9585..60726052bbd6 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funkcije kao što su Quick Share i Pronađi moj uređaj koriste Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth će se uključiti sutra ujutru"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Deli zvuk"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deli se zvuk"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivo baterije je <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nije podešen"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"unesite otključavanje ekrana"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor za otisak prsta"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"potvrdite identitet"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"unesite uređaj"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index ed44819ef2a0..df1227fc11e2 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Bluetooth выкарыстоўваецца такімі функцыямі і сэрвісамі, як Хуткае абагульванне і Знайсці прыладу"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth уключыцца заўтра раніцай"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Абагуліць аўдыя"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Ідзе абагульванне аўдыя"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Узровень зараду: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Гук"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string> @@ -379,7 +383,7 @@ <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Пачаць"</string> <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Спыніць"</string> <string name="qs_record_issue_label" msgid="8166290137285529059">"Запіс праблемы"</string> - <string name="qs_record_issue_start" msgid="2979831312582567056">"Пачынайце"</string> + <string name="qs_record_issue_start" msgid="2979831312582567056">"Пачаць"</string> <string name="qs_record_issue_stop" msgid="3531747965741982657">"Спыніцеся"</string> <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Справаздача"</string> <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"З чым была звязана праблема, якая вам сустрэлася?"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Націсніце, каб убачыць больш"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Няма будзільнікаў"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"прыступіць да разблакіроўкі экрана"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер адбіткаў пальцаў"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"правесці аўтэнтыфікацыю"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"адкрыць галоўны экран прылады"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index b88e0e4cde15..b7c8467ce9c9 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Докоснете за още информация"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Няма зададен будилник"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"въведете опция за заключване на екрана"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отпечатъци"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"удостоверяване"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"вход в устройството"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 0e44b3c60deb..9fa4c6efd8c0 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"আরও তথ্যের জন্য ট্যাপ করুন"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"কোনও অ্যালার্ম সেট করা নেই"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"স্ক্রিন লক খুলুন"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ফিঙ্গারপ্রিন্ট সেন্সর"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"যাচাই করিয়ে নিন"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ডিভাইস আনলক করুন"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index a7f90687b1ec..8dee87a6f736 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1195,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nema nijednog alarma"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"unos zaključavanja ekrana"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor za otisak prsta"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentificiranje"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"pristup uređaju"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 4f055d41199c..f9d343f8ee5b 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Les funcions com Quick Share i Troba el meu dispositiu utilitzen el Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"El Bluetooth s\'activarà demà al matí"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Comparteix l\'àudio"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"S\'està compartint l\'àudio"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Àudio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculars"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca per obtenir més informació"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Cap alarma definida"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"utilitza el bloqueig de pantalla"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor d\'empremtes digitals"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"accedir al dispositiu"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index e50fea56fdf4..3caa23de51d6 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Klepnutím zobrazíte další informace"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Budík nenastaven"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"zadejte zámek obrazovky"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Snímač otisků prstů"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ověříte"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"zadáte zařízení"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 310f2b195759..5333768f9a15 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funktioner som f.eks. Quick Share og Find min enhed anvender Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth aktiveres i morgen tidlig"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Del lyd"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deler lyd"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tryk for at få flere oplysninger"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ingen alarm er indstillet"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"angiv skærmlås"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeraftrykssensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"godkende"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"få adgang til enheden"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 27f6b57aab22..847e25f74f38 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funktionen wie „Quick Share“ und „Mein Gerät finden“ verwenden Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth wird morgen früh aktiviert"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Audioinhalte freigeben"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audioinhalte werden freigegeben"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkustand: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Für weitere Informationen tippen"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Kein Wecker gestellt"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"Displaysperre eingeben"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerabdrucksensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authentifizieren"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"Eingeben des Geräts"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index e2480b2eaf82..80fe3d2acb32 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Πατήστε για περισσότερες πληροφορίες."</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Δεν ορίστηκε ξυπνητ."</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"εισαγωγή κλειδώματος οθόνης"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Αισθητήρας δακτυλικών αποτυπωμάτων"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"έλεγχος ταυτότητας"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"εισαγωγή συσκευής"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 303e9220a2f3..498031f20074 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Features like Quick Share and Find My Device use Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Share audio"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Sharing audio"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 042d06e6eca1..8975cbf5c2bb 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -125,26 +125,19 @@ <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string> <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string> <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string> - <!-- no translation found for screenrecord_stop_dialog_title (2685522129492260887) --> - <skip /> - <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> - <skip /> - <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> - <skip /> - <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> - <skip /> - <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> - <skip /> - <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> - <skip /> - <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> - <skip /> - <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> - <skip /> - <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> - <skip /> - <!-- no translation found for close_dialog_button (4749497706540104133) --> - <skip /> + <string name="screenrecord_stop_dialog_title" msgid="2685522129492260887">"Stop recording screen?"</string> + <string name="screenrecord_stop_dialog_message" msgid="1926783607059442889">"You will stop recording your screen"</string> + <string name="screenrecord_stop_dialog_message_specific_app" msgid="5285148796772616326">"You will stop recording <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>"</string> + <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"Stop recording"</string> + <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"Stop sharing screen?"</string> + <string name="share_to_app_stop_dialog_message" msgid="3181723638915877339">"You will stop sharing your screen"</string> + <string name="share_to_app_stop_dialog_message_specific_app" msgid="124371406810544777">"You will stop sharing <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>"</string> + <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Stop sharing"</string> + <string name="cast_to_other_device_stop_dialog_title" msgid="1910372600290258193">"Stop casting screen?"</string> + <string name="cast_to_other_device_stop_dialog_message" msgid="1502520537030715412">"You will stop casting your screen"</string> + <string name="cast_to_other_device_stop_dialog_message_specific_app" msgid="4891536209254041850">"You will stop casting <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>"</string> + <string name="cast_to_other_device_stop_dialog_button" msgid="6420183747435521834">"Stop casting"</string> + <string name="close_dialog_button" msgid="4749497706540104133">"Close"</string> <string name="issuerecord_title" msgid="286627115110121849">"Issue Recorder"</string> <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Processing issue recording"</string> <string name="issuerecord_channel_description" msgid="6142326363431474632">"Ongoing notification for an issue collection session"</string> @@ -155,8 +148,7 @@ <string name="issuerecord_save_error" msgid="6913040083446722726">"Error saving issue recording"</string> <string name="issuerecord_start_error" msgid="3402782952722871190">"Error starting issue recording"</string> <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string> - <!-- no translation found for immersive_cling_description (2717426731830851921) --> - <skip /> + <string name="immersive_cling_description" msgid="2717426731830851921">"To exit, swipe down from the top of your screen"</string> <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string> <string name="accessibility_back" msgid="6530104400086152611">"Back"</string> <string name="accessibility_home" msgid="5430449841237966217">"Home"</string> @@ -299,8 +291,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string> - <!-- no translation found for turn_on_bluetooth_auto_tomorrow (3345758139235739006) --> - <skip /> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="3345758139235739006">"Automatically turn on tomorrow"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Features like Quick Share and Find My Device use Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Share audio"</string> @@ -1195,6 +1186,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authenticate"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string> @@ -1345,14 +1338,10 @@ <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string> <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string> <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string> - <!-- no translation found for touchpad_tutorial_back_gesture_button (2746834288077265946) --> - <skip /> - <!-- no translation found for touchpad_tutorial_home_gesture_button (7640544867625955304) --> - <skip /> - <!-- no translation found for touchpad_tutorial_action_key_button (3220074511852927267) --> - <skip /> - <!-- no translation found for touchpad_tutorial_done_button (176168488821755503) --> - <skip /> + <string name="touchpad_tutorial_back_gesture_button" msgid="2746834288077265946">"Back gesture"</string> + <string name="touchpad_tutorial_home_gesture_button" msgid="7640544867625955304">"Home gesture"</string> + <string name="touchpad_tutorial_action_key_button" msgid="3220074511852927267">"Action key"</string> + <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Done"</string> <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Keyboard backlight"</string> <string name="keyboard_backlight_value" msgid="7336398765584393538">"Level %1$d of %2$d"</string> <string name="home_controls_dream_label" msgid="6567105701292324257">"Home Controls"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 303e9220a2f3..498031f20074 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Features like Quick Share and Find My Device use Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Share audio"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Sharing audio"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 303e9220a2f3..498031f20074 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Features like Quick Share and Find My Device use Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Share audio"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Sharing audio"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 5a5af968740a..179b792ad50e 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -125,26 +125,19 @@ <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string> <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string> <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string> - <!-- no translation found for screenrecord_stop_dialog_title (2685522129492260887) --> - <skip /> - <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> - <skip /> - <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> - <skip /> - <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> - <skip /> - <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> - <skip /> - <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> - <skip /> - <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> - <skip /> - <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> - <skip /> - <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> - <skip /> - <!-- no translation found for close_dialog_button (4749497706540104133) --> - <skip /> + <string name="screenrecord_stop_dialog_title" msgid="2685522129492260887">"Stop recording screen?"</string> + <string name="screenrecord_stop_dialog_message" msgid="1926783607059442889">"You will stop recording your screen"</string> + <string name="screenrecord_stop_dialog_message_specific_app" msgid="5285148796772616326">"You will stop recording <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>"</string> + <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"Stop recording"</string> + <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"Stop sharing screen?"</string> + <string name="share_to_app_stop_dialog_message" msgid="3181723638915877339">"You will stop sharing your screen"</string> + <string name="share_to_app_stop_dialog_message_specific_app" msgid="124371406810544777">"You will stop sharing <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>"</string> + <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Stop sharing"</string> + <string name="cast_to_other_device_stop_dialog_title" msgid="1910372600290258193">"Stop casting screen?"</string> + <string name="cast_to_other_device_stop_dialog_message" msgid="1502520537030715412">"You will stop casting your screen"</string> + <string name="cast_to_other_device_stop_dialog_message_specific_app" msgid="4891536209254041850">"You will stop casting <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>"</string> + <string name="cast_to_other_device_stop_dialog_button" msgid="6420183747435521834">"Stop casting"</string> + <string name="close_dialog_button" msgid="4749497706540104133">"Close"</string> <string name="issuerecord_title" msgid="286627115110121849">"Issue Recorder"</string> <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Processing issue recording"</string> <string name="issuerecord_channel_description" msgid="6142326363431474632">"Ongoing notification for an issue collection session"</string> @@ -155,8 +148,7 @@ <string name="issuerecord_save_error" msgid="6913040083446722726">"Error saving issue recording"</string> <string name="issuerecord_start_error" msgid="3402782952722871190">"Error starting issue recording"</string> <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string> - <!-- no translation found for immersive_cling_description (2717426731830851921) --> - <skip /> + <string name="immersive_cling_description" msgid="2717426731830851921">"To exit, swipe down from the top of your screen"</string> <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string> <string name="accessibility_back" msgid="6530104400086152611">"Back"</string> <string name="accessibility_home" msgid="5430449841237966217">"Home"</string> @@ -299,8 +291,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string> - <!-- no translation found for turn_on_bluetooth_auto_tomorrow (3345758139235739006) --> - <skip /> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="3345758139235739006">"Automatically turn on tomorrow"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Features like Quick Share and Find My Device use Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Share audio"</string> @@ -1195,6 +1186,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authenticate"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string> @@ -1345,14 +1338,10 @@ <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string> <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string> <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string> - <!-- no translation found for touchpad_tutorial_back_gesture_button (2746834288077265946) --> - <skip /> - <!-- no translation found for touchpad_tutorial_home_gesture_button (7640544867625955304) --> - <skip /> - <!-- no translation found for touchpad_tutorial_action_key_button (3220074511852927267) --> - <skip /> - <!-- no translation found for touchpad_tutorial_done_button (176168488821755503) --> - <skip /> + <string name="touchpad_tutorial_back_gesture_button" msgid="2746834288077265946">"Back gesture"</string> + <string name="touchpad_tutorial_home_gesture_button" msgid="7640544867625955304">"Home gesture"</string> + <string name="touchpad_tutorial_action_key_button" msgid="3220074511852927267">"Action key"</string> + <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Done"</string> <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Keyboard backlight"</string> <string name="keyboard_backlight_value" msgid="7336398765584393538">"Level %1$d of %2$d"</string> <string name="home_controls_dream_label" msgid="6567105701292324257">"Home Controls"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index ce5bd5e661da..0cad6b71dbb9 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Presiona para obtener más información"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No establecida"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ingresa el bloqueo pantalla"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de huellas dactilares"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ingresar al dispositivo"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 000db75c6755..24638f57a032 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Las funciones como Quick Share y Encontrar mi dispositivo usan Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"El Bluetooth se activará mañana por la mañana"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Compartir audio"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Compartiendo audio"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca la pantalla para consultar más información"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ninguna puesta"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"Poner bloqueo de pantalla"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de huellas digitales"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticarte"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"acceder al dispositivo"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index fa10a8ab3f25..8169e8225a5f 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Puudutage lisateabe saamiseks"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Äratust pole"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"sisesta ekraanilukk"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sõrmejäljeandur"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentimiseks"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"seadmesse sisenemiseks"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 960a92fbefd8..e9e851832393 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Quick Share, Bilatu nire gailua eta beste eginbide batzuek Bluetootha erabiltzen dute"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bihar goizean aktibatuko da Bluetootha"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Partekatu audioa"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audioa partekatzen"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Entzungailua"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Informazio gehiago lortzeko, sakatu hau"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarmarik ez"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"erabili pantailaren blokeoa"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Hatz-marken sentsorea"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifikatu"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"sartu gailuan"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index f02c248eb071..3e876ad7a13d 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"ویژگیهایی مثل «همرسانی سریع» و «پیدا کردن دستگاهم» از بلوتوث استفاده میکنند"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"بلوتوث فردا صبح روشن خواهد شد"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"همرسانی صدا"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"درحال همرسانی صدا"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"شارژ باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"هدست"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"برای اطلاعات بیشتر ضربه بزنید"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"هشداری تنظیم نشده"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"وارد کردن قفل صفحه"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"حسگر اثرانگشت"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"اصالتسنجی کردن"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"وارد شدن به دستگاه"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 9a24082be9fc..2291d19d892d 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Saat lisätietoja napauttamalla"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ei herätyksiä"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"käytä näytön lukitustapaa"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sormenjälkitunnistin"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"todentaaksesi"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"avataksesi laitteen"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 77335de3d4cf..fbd16d4abb45 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Touchez pour en savoir plus"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Aucune alarme définie"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"entrer verrouillage de l\'écran"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Capteur d\'empreintes digitales"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authentifier"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"accéder à l\'appareil"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 98bfc7284045..55c39dc219f2 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Appuyer pour en savoir plus"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Pas d\'alarme définie"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"accéder au verrouillage de l\'écran"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Lecteur d\'empreinte digitale"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"s\'authentifier"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"accéder à l\'appareil"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 28cf92a5585a..a85b9ec322b2 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca para obter máis información"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Sen alarmas postas"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir o bloqueo de pantalla"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impresión dixital"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"poñer o dispositivo"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 2914bad91ef5..11846b36883a 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"ક્વિક શેર અને Find My Device જેવી સુવિધાઓ બ્લૂટૂથનો ઉપયોગ કરે છે"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"બ્લૂટૂથ આવતીકાલે સવારે ચાલુ થશે"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ઑડિયો શેર કરો"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ઑડિયો શેર કરી રહ્યાં છીએ"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> બૅટરી"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ઑડિયો"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"હૅડસેટ"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"વધુ માહિતી માટે ટૅપ કરો"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"કોઈ અલાર્મ સેટ નથી"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"સ્ક્રીન લૉક દાખલ કરો"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ફિંગરપ્રિન્ટ સેન્સર"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ખાતરી કરો"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ડિવાઇસ અનલૉક કરો"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index a6bc9b5a5b45..dcd3b6cf29c7 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -382,7 +388,7 @@ <string name="qs_record_issue_start" msgid="2979831312582567056">"शुरू करें"</string> <string name="qs_record_issue_stop" msgid="3531747965741982657">"रोकें"</string> <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"गड़बड़ी की रिपोर्ट"</string> - <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"आपके डिवाइस की कौनसी सुविधा पर असर पड़ा था?"</string> + <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"आपके डिवाइस की किस सुविधा में समस्या आ रही थी?"</string> <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"समस्या का टाइप चुनें"</string> <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"स्क्रीन रिकॉर्डर"</string> <string name="performance" msgid="6552785217174378320">"परफ़ॉर्मेंस"</string> @@ -393,7 +399,7 @@ <string name="quick_settings_hearing_devices_connected" msgid="6519069502397037781">"ऐक्टिव"</string> <string name="quick_settings_hearing_devices_disconnected" msgid="8907061223998176187">"डिसकनेक्ट हो गया"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"कान की मशीनें"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"नया डिवाइस जोड़ें"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"कान की नई मशीन जोड़ें"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"नया डिवाइस जोड़ने के लिए क्लिक करें"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"प्रीसेट अपडेट नहीं किया जा सका"</string> <string name="hearing_devices_preset_label" msgid="7878267405046232358">"प्रीसेट"</string> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ज़्यादा जानकारी के लिए टैप करें"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"कोई अलार्म सेट नहीं है"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रीन लॉक डालें"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फ़िंगरप्रिंट सेंसर"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"पुष्टि करें"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिवाइस की होम स्क्रीन पर जाएं"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index ba82bdd3660d..1eff7d4cf9f6 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1195,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nema nijednog alarma"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"unesite zaključavanje zaslona"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor otiska prsta"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentificirali"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"pristupili uređaju"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index bb228079e764..c0905b5aac37 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Koppintással további információkat érhet el."</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nincs ébresztés"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"képernyőzár megadása"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Ujjlenyomat-érzékelő"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"a hitelesítéshez"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"eszköz megadásához"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 14db87a61dea..41593d86b581 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Հպեք՝ ավելին իմանալու համար"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Զարթուցիչ դրված չէ"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ապակողպել էկրանը"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Մատնահետքի սկաներ"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"նույնականացնել"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"նշել սարքը"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index f9f63701e2d2..831adf9c2733 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Fitur seperti Quick Share dan Temukan Perangkat Saya menggunakan Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth akan dinyalakan besok pagi"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Bagikan audio"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Berbagi audio"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ketuk untuk informasi selengkapnya"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm tidak disetel"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"masukkan kunci layar"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor sidik jari"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentikasi"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"masukkan perangkat"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 0266cb202af1..a73bd7ab771f 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Eiginleikar eins og Flýtideiling og Finna tækið mitt nota Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Kveikt verður á Bluetooth í fyrramálið"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Deila hljóði"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deilir hljóði"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> rafhlöðuhleðsla"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hljóð"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Höfuðtól"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ýttu til að fá frekari upplýsingar"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Enginn vekjari"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"sláðu inn skjálás"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingrafaralesari"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"auðkenna"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"opna tæki"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 9cc5124d1508..a424f20eb1e7 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1195,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tocca per ulteriori informazioni"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nessuna"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"inserisci blocco schermo"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensore di impronte digitali"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"effettuare l\'autenticazione"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"accedere al dispositivo"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 734e7da5cd49..4e0977e6a0c6 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"יש להקיש כדי להציג מידע נוסף"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"לא הוגדרה"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"הזנת קוד נעילת המסך"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"חיישן טביעות אצבע"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"אימות"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"הזנת מכשיר"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 6e629723771c..6c8504bbd421 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"タップすると詳細が表示されます"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"アラーム未設定"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"画面ロックを設定"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋認証センサー"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"認証"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"デバイスを入力"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 5914f127a8cd..c21dc1078ae1 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"შეეხეთ მეტი ინფორმაციისთვის"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"მაღვიძარა არ არის"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ეკრანის დაბლოკვის შეყვანა"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"თითის ანაბეჭდის სენსორი"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ავტორიზაცია"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"მოწყობილობის შეყვანა"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 40b91499abff..0a7b976ddb4d 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Толығырақ ақпарат алу үшін түртіңіз."</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Оятқыш орнатылмаған."</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"экран құлпын енгізу"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Саусақ ізін оқу сканері"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"аутентификациялау"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"құрылғыны енгізу"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 3836d7d96caa..20a9ae5a2c5c 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ចុចដើម្បីទទួលបានព័ត៌មានបន្ថែម"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"មិនបានកំណត់ម៉ោងរោទ៍ទេ"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"បញ្ចូលព័ត៌មានផ្ទៀងផ្ទាត់សម្រាប់ការចាក់សោអេក្រង់"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ឧបករណ៍ចាប់ស្នាមម្រាមដៃ"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ផ្ទៀងផ្ទាត់"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"បញ្ចូលឧបករណ៍"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 85d67cda60df..922075a9c757 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ಅಲಾರಾಂ ಸೆಟ್ ಆಗಿಲ್ಲ"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ನಮೂದಿಸಿ"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ದೃಢೀಕರಿಸಿ"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ಸಾಧನವನ್ನು ಪ್ರವೇಶಿಸಿ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 4c26ed21577a..496307ae0d3c 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"탭하여 자세한 정보를 확인하세요."</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"설정된 알람 없음"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"화면 잠금 입력"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"지문 센서"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"인증"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"기기 입력"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index ae91a7c48939..2e62142150fc 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Кеңири маалымат алуу үчүн таптап коюңуз"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ойготкуч коюлган жок"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"экран кулпусун киргизүү"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Манжа изинин сенсору"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"аныктыгын текшерүү"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"түзмөккө кирүү"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 30f8a656a2ef..fb3b3779b399 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ແຕະເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ບໍ່ໄດ້ຕັ້ງໂມງປຸກ"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ໃສ່ຂໍ້ມູນການລັອກໜ້າຈໍ"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ເຊັນເຊີລາຍນິ້ວມື"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ພິສູດຢືນຢັນ"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ເຂົ້າອຸປະກອນ"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 4fffb3d904ce..cca7b7f00e28 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Tokioms funkcijoms kaip „Spartusis bendrinimas“ ir „Rasti įrenginį“ naudojamas „Bluetooth“ ryšys"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"„Bluetooth“ ryšys bus įjungtas rytoj ryte"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Bendrinti garsą"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Bendrinamas garsas"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumuliatorius: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Garsas"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Virtualiosios realybės įrenginys"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Palieskite, kad sužinotumėte daugiau informacijos"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenustatyta signalų"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"įvesti ekrano užraktą"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Kontrolinio kodo jutiklis"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"nustatytumėte tapatybę"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"pasiektumėte įrenginį"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 28b15d0a7b1a..7c72c7e72bc7 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Pieskarieties, lai iegūtu plašāku informāciju."</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nav iestatīts signāls"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ievadīt ekrāna bloķēšanas informāciju"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Pirksta nospieduma sensors"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"veiktu autentificēšanu"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"izmantotu ierīci"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index bf71f8eb29f1..a8baf35147e6 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Допрете за повеќе информации"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Не е поставен аларм"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"внесете PIN/шема/лозинка"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отпечатоци"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"автентицирате"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"внесете уред"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 2031af047ebf..9f19680a4729 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"കൂടുതൽ വിവരങ്ങൾക്ക് ടാപ്പ് ചെയ്യുക"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"അലാറം സജ്ജീകരിച്ചിട്ടില്ല"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"സ്ക്രീൻ ലോക്ക് നൽകുക"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ഫിംഗർപ്രിന്റ് സെൻസർ"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"പരിശോധിച്ചുറപ്പിക്കുക"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ഉപകരണം നൽകുക"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 556d205aa085..a6ab892fd233 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Түргэн хуваалцах, Миний төхөөрөмжийг олох зэрэг онцлогууд Bluetooth-г ашигладаг"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth-г маргааш өглөө асаана"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Аудио хуваалцах"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Аудио хуваалцаж байна"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батарей"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Чихэвч"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Нэмэлт мэдээлэл авахын тулд товшино уу"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Сэрүүлэг тавиагүй"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"дэлгэцийн түгжээ оруулах"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Хурууны хээ мэдрэгч"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"баталгаажуулах"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"төхөөрөмж оруулах"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index a009fe11f307..cc6e59a1a8a6 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"क्विक शेअर आणि Find My Device यांसारखी वैशिष्ट्ये ब्लूटूथ वापरतात"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ब्लूटूथ उद्या सकाळी सुरू होईल"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ऑडिओ शेअर करा"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ऑडिओ शेअर करत आहे"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बॅटरी"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडिओ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"अधिक माहितीसाठी टॅप करा"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"अलार्म सेट केला नाही"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रीन लॉक एंटर करा"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फिंगरप्रिंट सेन्सर"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ऑथेंटिकेट करा"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिव्हाइस एंटर करा"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 59c4071f82d5..ced6541ebd96 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ketik untuk mendapatkan maklumat lanjut"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Tiada penggera"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"masukkan kunci skrin"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Penderia cap jari"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"sahkan"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"akses peranti"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index d13ee635c682..eae8e1dd4f07 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -106,7 +106,7 @@ <string name="screenrecord_title" msgid="4257171601439507792">"ဖန်သားပြင်ရိုက်ကူးစက်"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"စကရင်ရိုက်ကူးမှု အပြီးသတ်နေသည်"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"ဖန်သားပြင် ရိုက်ကူးသည့် စက်ရှင်အတွက် ဆက်တိုက်လာနေသော အကြောင်းကြားချက်"</string> - <string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"ရိုက်သံဖမ်းခြင်း စတင်မလား။"</string> + <string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"ရုပ်သံဖမ်းခြင်း စတင်မလား။"</string> <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"ရုပ်သံဖမ်းနေစဉ် Android သည် သင့်ဖန်သားပြင်တွင် မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string> <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"အက်ပ်တစ်ခုကို ရုပ်သံဖမ်းနေစဉ် Android သည် ယင်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string> <string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"ရုပ်သံ စဖမ်းရန်"</string> @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"‘အမြန် မျှဝေပါ’ နှင့် Find My Device ကဲ့သို့ တူးလ်များသည် ဘလူးတုသ်သုံးသည်"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"မနက်ဖြန်နံနက်တွင် ဘလူးတုသ် ပွင့်ပါမည်"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"အသံမျှဝေရန်"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"အသံမျှဝေနေသည်"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ဘက်ထရီ"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"အသံ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"မိုက်ခွက်ပါနားကြပ်"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"နောက်ထပ်အချက်အလက်များအတွက် တို့ပါ"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"နှိုးစက်ပေးမထားပါ"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ဖန်သားပြင်လော့ခ် ထည့်ရန်"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"လက်ဗွေ အာရုံခံကိရိယာ"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"အထောက်အထားစိစစ်ရန်"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"စက်ပစ္စည်းသို့ ဝင်ရန်"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 91ef46672a0a..ebd37f495cce 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funksjoner som Quick Share og Finn enheten min bruker Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth slås på i morgen tidlig"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Del lyd"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deler lyd"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Hodetelefoner"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Trykk for å få mer informasjon"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ingen alarm angitt"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"legg inn skjermlåsen"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeravtrykkssensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentiser"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"åpne enheten"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 10d64b79383a..f83f77ce8b20 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"थप जानकारी प्राप्त गर्न ट्याप गर्नुहोस्"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"अलार्म राखिएको छैन"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रिन लक हाल्नुहोस्"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फिंगरप्रिन्ट सेन्सर"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"प्रमाणित गर्नुहोस्"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिभाइस हाल्नुहोस्"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index c990cab72e5b..110b59effc0d 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tik hier voor meer informatie"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Geen wekker gezet"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"schermvergrendeling invoeren"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Vingerafdruksensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"verifiëren"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"apparaat opgeven"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 49a3268efca4..2c60c2b0ca8f 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ଅଧିକ ସୂଚନା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ଆଲାରାମ ସେଟ ହୋଇନାହିଁ"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ସ୍କ୍ରିନ ଲକ ଲେଖନ୍ତୁ"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ଟିପଚିହ୍ନ ସେନ୍ସର୍"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ପ୍ରମାଣୀକରଣ କରନ୍ତୁ"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ଡିଭାଇସ୍ ବିଷୟରେ ସୂଚନା ଲେଖନ୍ତୁ"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 6a7e9e839915..54a763dc57f9 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ਕੋਈ ਅਲਾਰਮ ਸੈੱਟ ਨਹੀਂ"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ਸਕ੍ਰੀਨ ਲਾਕ ਦਾਖਲ ਕਰੋ"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ਪ੍ਰਮਾਣਿਤ ਕਰੋ"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ਡੀਵਾਈਸ ਵਿੱਚ ਦਾਖਲ ਹੋਵੋ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index c154fc9bb2f3..39e243d458b9 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Bluetootha używają funkcje takie jak szybkie udostępnianie czy Znajdź moje urządzenie"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth włączy się jutro rano"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Udostępnij dźwięk"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Udostępnia dźwięk"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> naładowania baterii"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Dźwięk"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Zestaw słuchawkowy"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Kliknij, aby uzyskać więcej informacji"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nie ustawiono alarmu"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"Wprowadź blokadę ekranu"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Czytnik linii papilarnych"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"uwierzytelnij"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"otwórz urządzenie"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 2e643910604d..dc7d56495997 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para mais informações"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme definido"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"inserir bloqueio de tela"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressão digital"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"acessar o dispositivo"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index ca0f1715afb5..0b749415888f 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para obter mais informações"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme definido"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"introduzir bloqueio de ecrã"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressões digitais"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"entrar no dispositivo"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 2e643910604d..dc7d56495997 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para mais informações"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme definido"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"inserir bloqueio de tela"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressão digital"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"acessar o dispositivo"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 36457fd8fbb7..f735be494ef7 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Atinge pentru mai multe informații"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nicio alarmă setată"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"intră în blocarea ecranului"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor de amprentă"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifică-te"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"Accesează dispozitivul"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index fe0e8db7f0e0..d7973fdb900f 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Нажмите, чтобы узнать больше."</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Нет будильников"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"разблокировать экран"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер отпечатков пальцев"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"выполнить аутентификацию"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"указать устройство"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 8357008283d4..dac0c42251b1 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"තවත් තොරතුරු සඳහා තට්ටු කරන්න"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"එලාම සකසා නැත"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"තිර අගුල ඇතුළු කරන්න"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ඇඟිලි සලකුණු සංවේදකය"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"සත්යාපනය කරන්න"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"උපාංගය ඇතුළු කරන්න"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 148b3909daa5..0014117f7433 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funkcie ako Quick Share a Nájdi moje zariadenie používajú Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth sa zapne zajtra ráno"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Zdieľať zvuk"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Zdieľa sa zvuk"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batéria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Náhlavná súprava"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Klepnutím si zobrazíte ďalšie informácie"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Žiadny budík"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"zadať zámku obrazovky"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor odtlačkov prstov"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"overte"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"vstúpte do zariadenia"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 489551d1c308..4428c0c8bc0d 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dotaknite se za več informacij"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ni nastavljenih alarmov"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"odklenite zaslon"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Tipalo prstnih odtisov"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"preverjanje pristnosti"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"vstop v napravo"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 5b74b1e487e6..537134d5342c 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Veçoritë e tilla si \"Ndarja e shpejtë\" dhe \"Gjej pajisjen time\" përdorin Bluetooth-in"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth-i do të aktivizohet nesër në mëngjes"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Ndaj audion"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audioja po ndahet"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kufje me mikrofon"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Trokit për më shumë informacione"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nuk është caktuar asnjë alarm"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"hyr te kyçja e ekranit"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensori i gjurmës së gishtit"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"për ta vërtetuar"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"për të hyrë në pajisje"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 18772744d19d..1969746ac71a 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Функције као што су Quick Share и Пронађи мој уређај користе Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth ће се укључити сутра ујутру"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Дели звук"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Дели се звук"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Ниво батерије је <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалице"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Додирните за више информација"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Није подешен"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"унесите откључавање екрана"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отисак прста"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"потврдите идентитет"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"унесите уређај"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index dc4dea1e4a1a..6f2c07b97298 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tryck för mer information"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Inget inställt alarm"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ange skärmlåset"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeravtryckssensor"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentisera"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ange enhet"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 7bfef43bf2cb..a15d975f19a7 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Gusa ili upate maelezo zaidi"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Hujaweka kengele"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"weka kifunga skrini"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Kitambua alama ya kidole"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"thibitisha"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"weka kifaa"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 62983630f8d3..c9e17ff462d5 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"மேலும் தகவல்களுக்கு தட்டவும்"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"அலாரம் எதுவுமில்லை"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"திரைப் பூட்டை உள்ளிடலாம்"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"கைரேகை சென்சார்"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"அங்கீகரி"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"சாதனத்தைத் திற"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index e3238fe2b690..4917ff612717 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"క్విక్ షేర్, Find My Device వంటి ఫీచర్లు బ్లూటూత్ను ఉపయోగిస్తాయి"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"బ్లూటూత్ రేపు ఉదయం ఆన్ అవుతుంది"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ఆడియోను షేర్ చేయండి"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ఆడియోను షేర్ చేస్తున్నారు"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> బ్యాటరీ"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ఆడియో"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"హెడ్సెట్"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"మరింత సమాచారం కోసం ట్యాప్ చేయండి"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"అలారం సెట్ చేయలేదు"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"స్క్రీన్ లాక్ను ఎంటర్ చేయండి"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"వేలిముద్ర సెన్సార్"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ప్రామాణీకరించండి"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"పరికరాన్ని ఎంటర్ చేయండి"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index d21bf5e2d3b7..f4914f0e0c45 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"แตะดูข้อมูลเพิ่มเติม"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ไม่มีการตั้งปลุก"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ป้อนข้อมูลการล็อกหน้าจอ"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"เซ็นเซอร์ลายนิ้วมือ"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ตรวจสอบสิทธิ์"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"เข้าถึงอุปกรณ์"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index b63d282b3ffc..89fa5ea560f8 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"I-tap para sa higit pang impormasyon"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Walang alarm"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ilagay ang lock ng screen"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor para sa fingerprint"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"i-authenticate"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"ilagay ang device"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index f78afe44e0a0..bf6e6aaaad39 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Daha fazla bilgi için dokunun"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm yok"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran kilidini gir"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Parmak izi sensörü"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"kimlik doğrulaması yapın"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"cihaz girin"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 46d061983f81..620628991a6d 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Натисніть, щоб дізнатися більше"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Немає будильників"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"показати способи розблокування екрана"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер відбитків пальців"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"пройти автентифікацію"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"відкрити пристрій"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 38bddf7ceaf7..0aab4cedfe6b 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"فوری اشتراک اور \'میرا آلہ ڈھونڈیں\' جیسی خصوصیات بلوٹوتھ کا استعمال کرتی ہیں"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"بلوٹوتھ کل صبح آن ہو جائے گا"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"آڈیو کا اشتراک کریں"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"آڈیو کا اشتراک ہو رہا ہے"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> بیٹری"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"آڈیو"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ہیڈ سیٹ"</string> @@ -1201,6 +1205,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"مزید معلومات کے لیے تھپتھپائیں"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"کوئی الارم سیٹ نہیں ہے"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"اسکرین لاک درج کریں"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"فنگر پرنٹ سینسر"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"تصدیق کریں"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"آلہ درج کریں"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 52ecbc360b40..312737db058c 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Batafsil axborot olish uchun bosing"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Signal sozlanmagan"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran qulfini kiriting"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Barmoq izi skaneri"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifikatsiya"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"qurilmani ochish"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 041da411425f..f578e14a8f58 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Nhấn để biết thêm thông tin"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Chưa đặt chuông báo"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"nhập phương thức mở khoá màn hình"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Cảm biến vân tay"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"xác thực"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"truy cập thiết bị"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 9df7a500aa2b..ae1f2b035d92 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"快速分享、查找我的设备等功能会使用蓝牙"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"蓝牙将在明天早上开启"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"分享音频"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"正在分享音频"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> 的电量"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音频"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳机"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"点按即可了解详情"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未设置闹钟"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"输入屏幕解锁信息"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指纹传感器"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"身份验证"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"进入设备"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index fdb7759de125..35087f3a0a20 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"「快速共享」和「尋找我的裝置」等功能都會使用藍牙"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"藍牙將於明天上午開啟"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"分享音訊"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"正在分享音訊"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"輕按即可瞭解詳情"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未設定鬧鐘"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"輸入螢幕鎖定憑證"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋感應器"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"驗證"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"進入裝置"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index cbeb38a38c31..51fe3d7b323c 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -303,10 +309,8 @@ <skip /> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"「快速分享」和「尋找我的裝置」等功能都需要使用藍牙技術"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"藍牙會在明天早上開啟"</string> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (7545274861795853838) --> - <skip /> - <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (3069309588231072128) --> - <skip /> + <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"分享音訊"</string> + <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"正在分享音訊"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string> @@ -1197,6 +1201,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"輕觸即可瞭解詳情"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未設定鬧鐘"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"輸入螢幕鎖定憑證"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋感應器"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"驗證"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"進入裝置"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 2117568e0d7b..7b39f16ac1ee 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -129,18 +129,24 @@ <skip /> <!-- no translation found for screenrecord_stop_dialog_message (1926783607059442889) --> <skip /> + <!-- no translation found for screenrecord_stop_dialog_message_specific_app (5285148796772616326) --> + <skip /> <!-- no translation found for screenrecord_stop_dialog_button (2883812564938194350) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_title (9212915050910250438) --> <skip /> <!-- no translation found for share_to_app_stop_dialog_message (3181723638915877339) --> <skip /> + <!-- no translation found for share_to_app_stop_dialog_message_specific_app (124371406810544777) --> + <skip /> <!-- no translation found for share_to_app_stop_dialog_button (6334056916284230217) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_title (1910372600290258193) --> <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_message (1502520537030715412) --> <skip /> + <!-- no translation found for cast_to_other_device_stop_dialog_message_specific_app (4891536209254041850) --> + <skip /> <!-- no translation found for cast_to_other_device_stop_dialog_button (6420183747435521834) --> <skip /> <!-- no translation found for close_dialog_button (4749497706540104133) --> @@ -1197,6 +1203,8 @@ <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Thepha ukuze uthole olunye ulwazi"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Akukho alamu esethiwe"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"faka ukukhiya isikrini"</string> + <!-- no translation found for accessibility_side_fingerprint_indicator_label (1673807833352363712) --> + <skip /> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Inzwa yesigxivizo somunwe"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"gunyaza"</string> <string name="accessibility_enter_hint" msgid="2617864063504824834">"faka idivayisi"</string> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index ba59c2f99c52..b3d3021028e1 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -18,7 +18,6 @@ --> <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <drawable name="notification_number_text_color">#ffffffff</drawable> <drawable name="system_bar_background">@color/system_bar_background_opaque</drawable> <color name="system_bar_background_opaque">#ff000000</color> <color name="system_bar_background_transparent">#00000000</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index f8762f0a98d5..80b9ec7748b9 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -25,9 +25,6 @@ (package/class) --> <string name="config_recentsComponent" translatable="false">com.android.systemui.recents.OverviewProxyRecentsImpl</string> - <!-- Whether or not we show the number in the bar. --> - <bool name="config_statusBarShowNumber">false</bool> - <!-- For how long the lock screen can be on before the display turns off. --> <integer name="config_lockScreenDisplayTimeout">10000</integer> @@ -1049,4 +1046,7 @@ <!-- The width of the shortcut helper container, as a fraction of the screen's width. --> <item name="shortcut_helper_screen_width_fraction" format="float" type="dimen">1.0</item> + + <!-- Only applicable for dual shade - Allow Notifications/QS shade to anchor to the bottom. --> + <bool name="config_dualShadeAlignedToBottom">false</bool> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cf91326a4b76..40bdc3eb50f8 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -810,6 +810,8 @@ <dimen name="keyguard_smartspace_top_offset">12dp</dimen> <!-- The amount to translate lockscreen elements on the GONE->AOD transition --> <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen> + <!-- The amount to translate lockscreen elements on the GONE->AOD transition, on device fold --> + <dimen name="keyguard_enter_from_side_translation_x">-100dp</dimen> <dimen name="notification_scrim_corner_radius">32dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 838181252cb9..abafb01cc646 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -269,6 +269,8 @@ <string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string> <!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] --> <string name="app_clips_save_add_to_note">Add to note</string> + <!-- TODO(b/300307759): Temporary string for text view that displays backlinks data. [CHAR LIMIT=NONE] --> + <string name="backlinks_string" translatable="false">Open <xliff:g id="appName" example="Google Chrome">%1$s</xliff:g></string> <!-- Notification title displayed for screen recording [CHAR LIMIT=50]--> <string name="screenrecord_title">Screen Recorder</string> @@ -1222,6 +1224,12 @@ <string name="accessibility_action_label_remove_widget">remove widget</string> <!-- Label for accessibility action to place a widget in edit mode after selecting move widget. [CHAR LIMIT=NONE] --> <string name="accessibility_action_label_place_widget">place selected widget</string> + <!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] --> + <string name="communal_widgets_disclaimer_title">Lock screen widgets</string> + <!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] --> + <string name="communal_widgets_disclaimer_text">To open an app using a widget, you\u2019ll need to verify it\u2019s you. Also, keep in mind that anyone can view them, even when your tablet\u2019s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here.</string> + <!-- Button for user to verify they understand the information presented. [CHAR LIMIT=50] --> + <string name="communal_widgets_disclaimer_button">Got it</string> <!-- Related to user switcher --><skip/> @@ -1380,6 +1388,15 @@ <!-- Text which is shown in the expanded notification shade when there are currently no notifications visible that the user hasn't already seen. [CHAR LIMIT=30] --> <string name="no_unseen_notif_text">No new notifications</string> + <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=30] --> + <string name="adaptive_notification_edu_hun_title">Adaptive notifications is on</string> + + <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] --> + <string name="adaptive_notification_edu_hun_text">Your device now lowers the volume and reduces pop-ups on the screen for up to two minutes when you receive many notifications in a short time span.</string> + + <!-- Action label for going to adaptive notification settings [CHAR LIMIT=20] --> + <string name="go_to_adaptive_notification_settings">Turn off</string> + <!-- Text which is shown in the locked notification shade when there are currently no notifications, but if the user were to unlock, notifications would appear. [CHAR LIMIT=40] --> <string name="unlock_to_see_notif_text">Unlock to see older notifications</string> @@ -3577,6 +3594,12 @@ <string name="touchpad_tutorial_action_key_button">Action key</string> <!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] --> <string name="touchpad_tutorial_done_button">Done</string> + <!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_back_gesture_action_title">Go back</string> + <!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.</string> + <string name="touchpad_back_gesture_animation_content_description">Touchpad showing three fingers moving right and left</string> + <string name="touchpad_back_gesture_screen_animation_content_description">Device screen showing animation for back gesture</string> <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] --> <string name="keyboard_backlight_dialog_title">Keyboard backlight</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index f06e3335123f..78d4fc8d4c04 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -246,6 +246,9 @@ public class Task { public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData = new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData(); + @ViewDebug.ExportedProperty(category="recents") + public boolean isVisible; + public Task() { // Do nothing } @@ -279,6 +282,7 @@ public class Task { lastSnapshotData.set(other.lastSnapshotData); positionInParent = other.positionInParent; appBounds = other.appBounds; + isVisible = other.isVisible; } /** diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index 660f0db04204..2eac3931c2d7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -53,6 +53,9 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; @@ -142,12 +145,14 @@ public class RotationButtonController { }; private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() { + @WorkerThread @Override public void onRotationChanged(final int rotation) { + @Nullable Boolean rotationLocked = RotationPolicyUtil.isRotationLocked(mContext); // We need this to be scheduled as early as possible to beat the redrawing of // window in response to the orientation change. mMainThreadHandler.postAtFrontOfQueue(() -> { - onRotationWatcherChanged(rotation); + onRotationWatcherChanged(rotation, rotationLocked); }); } }; @@ -281,8 +286,8 @@ public class RotationButtonController { TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); } - public void setRotationLockedAtAngle(int rotationSuggestion, String caller) { - final Boolean isLocked = isRotationLocked(); + public void setRotationLockedAtAngle( + @Nullable Boolean isLocked, int rotationSuggestion, String caller) { if (isLocked == null) { // Ignore if we can't read the setting for the current user return; @@ -291,21 +296,6 @@ public class RotationButtonController { /* rotation= */ rotationSuggestion, caller); } - /** - * @return whether rotation is currently locked, or <code>null</code> if the setting couldn't - * be read - */ - public Boolean isRotationLocked() { - try { - return RotationPolicy.isRotationLocked(mContext); - } catch (SecurityException e) { - // TODO(b/279561841): RotationPolicy uses the current user to resolve the setting which - // may change before the rotation watcher can be unregistered - Log.e(TAG, "Failed to get isRotationLocked", e); - return null; - } - } - public void setRotateSuggestionButtonState(boolean visible) { setRotateSuggestionButtonState(visible, false /* force */); } @@ -469,7 +459,7 @@ public class RotationButtonController { * Called when the rotation watcher rotation changes, either from the watcher registered * internally in this class, or a signal propagated from NavBarHelper. */ - public void onRotationWatcherChanged(int rotation) { + public void onRotationWatcherChanged(int rotation, @Nullable Boolean isRotationLocked) { if (!mListenersRegistered) { // Ignore if not registered return; @@ -477,17 +467,16 @@ public class RotationButtonController { // If the screen rotation changes while locked, potentially update lock to flow with // new screen rotation and hide any showing suggestions. - Boolean rotationLocked = isRotationLocked(); - if (rotationLocked == null) { + if (isRotationLocked == null) { // Ignore if we can't read the setting for the current user return; } // The isVisible check makes the rotation button disappear when we are not locked // (e.g. for tabletop auto-rotate). - if (rotationLocked || mRotationButton.isVisible()) { + if (isRotationLocked || mRotationButton.isVisible()) { // Do not allow a change in rotation to set user rotation when docked. - if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) { - setRotationLockedAtAngle(rotation, /* caller= */ + if (shouldOverrideUserLockPrefs(rotation) && isRotationLocked && !mDocked) { + setRotationLockedAtAngle(true, rotation, /* caller= */ "RotationButtonController#onRotationWatcherChanged"); } setRotateSuggestionButtonState(false /* visible */, true /* forced */); @@ -592,7 +581,8 @@ public class RotationButtonController { private void onRotateSuggestionClick(View v) { mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED); incrementNumAcceptedRotationSuggestionsIfNeeded(); - setRotationLockedAtAngle(mLastRotationSuggestion, + setRotationLockedAtAngle( + RotationPolicyUtil.isRotationLocked(mContext), mLastRotationSuggestion, /* caller= */ "RotationButtonController#onRotateSuggestionClick"); Log.i(TAG, "onRotateSuggestionClick() mLastRotationSuggestion=" + mLastRotationSuggestion); v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationPolicyUtil.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationPolicyUtil.kt new file mode 100644 index 000000000000..eac4a10835d5 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationPolicyUtil.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.rotation + +import android.content.Context +import android.util.Log +import com.android.internal.view.RotationPolicy + +class RotationPolicyUtil { + companion object { + /** + * Recommend to be called on bg thread, or reuse the results. It's because + * [RotationPolicy.isRotationLocked] may make a binder call to query settings. + * + * @return whether rotation is currently locked, or <code>null</code> if the setting + * couldn't be read + */ + @JvmStatic + fun isRotationLocked(context: Context): Boolean? { + try { + return RotationPolicy.isRotationLocked(context) + } catch (e: SecurityException) { + // TODO(b/279561841): RotationPolicy uses the current user to resolve the setting + // which may change before the rotation watcher can be unregistered + Log.e("RotationPolicy", "Failed to get isRotationLocked", e) + return null + } + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 70465bc3d0b7..421782039043 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -1097,7 +1097,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation); AnimatorSet anims = new AnimatorSet(); - ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation); + ObjectAnimator yAnim = ObjectAnimator.ofFloat(mViewFlipper, View.TRANSLATION_Y, + yTranslation); ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA, 0f); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 5458ab1196d7..b37ba8994e44 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -1631,8 +1631,23 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold boolean bRTL = isRTL(mContext); final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3; - final int maxHeightSize = mWindowBounds.height() - 2 * mMirrorSurfaceMargin; - final int maxWidthSize = mWindowBounds.width() - 2 * mMirrorSurfaceMargin; + int maxHeightSize; + int maxWidthSize; + if (Flags.redesignMagnificationWindowSize()) { + // mOuterBorderSize = transparent margin area + // mMirrorSurfaceMargin = transparent margin area + orange border width + // We would like to allow the width and height to be full size. Therefore, the max + // frame size could be calculated as (window bounds - 2 * orange border width). + maxHeightSize = + mWindowBounds.height() - 2 * (mMirrorSurfaceMargin - mOuterBorderSize); + maxWidthSize = + mWindowBounds.width() - 2 * (mMirrorSurfaceMargin - mOuterBorderSize); + } else { + maxHeightSize = + mWindowBounds.height() - 2 * mMirrorSurfaceMargin; + maxWidthSize = + mWindowBounds.width() - 2 * mMirrorSurfaceMargin; + } Rect tempRect = new Rect(); tempRect.set(mMagnificationFrame); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index dafd5f89f417..030d147e21c3 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -72,6 +72,7 @@ class MenuAnimationController { private final Handler mHandler; private boolean mIsFadeEffectEnabled; private Runnable mSpringAnimationsEndAction; + private PointF mAnimationEndPosition = new PointF(); // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link // DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler @@ -104,10 +105,12 @@ class MenuAnimationController { @Override public void onRadiiAnimationStop() {} }); + mAnimationEndPosition = mMenuView.getMenuPosition(); } void moveToPosition(PointF position) { moveToPosition(position, /* animateMovement = */ false); + mAnimationEndPosition = position; } /* Moves position without updating underlying percentage position. Can be animated. */ @@ -129,6 +132,7 @@ class MenuAnimationController { } else { DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX); } + mAnimationEndPosition.x = positionX; } void moveToPositionY(float positionY) { @@ -144,6 +148,7 @@ class MenuAnimationController { } else { DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY); } + mAnimationEndPosition.y = positionY; } void moveToPositionYIfNeeded(float positionY) { @@ -259,6 +264,9 @@ class MenuAnimationController { cancelAnimation(property); mPositionAnimations.put(property, flingAnimation); + if (finalPosition != null) { + setAnimationEndPosition(property, finalPosition); + } flingAnimation.start(); } @@ -292,6 +300,7 @@ class MenuAnimationController { cancelAnimation(property); mPositionAnimations.put(property, springAnimation); + setAnimationEndPosition(property, finalPosition); springAnimation.animateToFinalPosition(finalPosition); } @@ -385,6 +394,21 @@ class MenuAnimationController { mPositionAnimations.get(property).cancel(); } + private void setAnimationEndPosition( + DynamicAnimation.ViewProperty property, Float endPosition) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + mAnimationEndPosition.x = endPosition; + } + if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + mAnimationEndPosition.y = endPosition; + } + } + + void skipAnimations() { + cancelAnimations(); + moveToPosition(mAnimationEndPosition, false); + } + @VisibleForTesting DynamicAnimation getAnimation(DynamicAnimation.ViewProperty property) { return mPositionAnimations.getOrDefault(property, null); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 0c67c5093faf..9d573d3919b9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -205,8 +205,9 @@ class MenuViewLayer extends FrameLayout implements return; } - setAccessibilityServiceState(getContext(), serviceComponentName, /* enabled= */ - false); + setAccessibilityServiceState( + getContext(), serviceComponentName, /* enabled= */ false, + mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)); }); } @@ -334,6 +335,7 @@ class MenuViewLayer extends FrameLayout implements mDragToInteractView.updateResources(); mDismissView.updateResources(); mDragToInteractAnimationController.updateResources(); + mMenuAnimationController.skipAnimations(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index c08756f6ae36..5e2b5ff5c1ac 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -74,7 +74,7 @@ interface PromptSelectorInteractor { val isConfirmationRequired: Flow<Boolean> /** Fingerprint sensor type */ - val sensorType: Flow<FingerprintSensorType> + val fingerprintSensorType: Flow<FingerprintSensorType> /** Switch to the credential view. */ fun onSwitchToCredential() @@ -154,7 +154,8 @@ constructor( } } - override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType + override val fingerprintSensorType: Flow<FingerprintSensorType> = + fingerprintPropertyRepository.sensorType override fun onSwitchToCredential() { val modalities: BiometricModalities = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index 9cc46506cefb..9578da4238ee 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -139,6 +139,11 @@ constructor( overlayView!!.visibility = View.INVISIBLE Log.d(TAG, "show(): adding overlayView $overlayView") windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams) + overlayView!!.announceForAccessibility( + applicationContext.resources.getString( + R.string.accessibility_side_fingerprint_indicator_label + ) + ) } /** Hide the side fingerprint sensor indicator */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt index 7081661708de..6c6ef5ac175e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt @@ -21,7 +21,6 @@ import android.annotation.DrawableRes import android.annotation.RawRes import android.content.res.Configuration import android.graphics.Rect -import android.hardware.face.Face import android.util.RotationUtils import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor @@ -137,7 +136,7 @@ constructor( displayStateInteractor.currentRotation, displayStateInteractor.isFolded, displayStateInteractor.isInRearDisplayMode, - promptSelectorInteractor.sensorType, + promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, promptViewModel.showingError @@ -183,7 +182,7 @@ constructor( displayStateInteractor.currentRotation, displayStateInteractor.isFolded, displayStateInteractor.isInRearDisplayMode, - promptSelectorInteractor.sensorType, + promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, promptViewModel.isPendingConfirmation, @@ -330,7 +329,7 @@ constructor( AuthType.Coex -> combine( displayStateInteractor.currentRotation, - promptSelectorInteractor.sensorType, + promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, promptViewModel.showingError @@ -430,7 +429,7 @@ constructor( AuthType.Fingerprint, AuthType.Coex -> combine( - promptSelectorInteractor.sensorType, + promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, promptViewModel.isPendingConfirmation, @@ -508,7 +507,7 @@ constructor( when (activeAuthType) { AuthType.Fingerprint -> combine( - promptSelectorInteractor.sensorType, + promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, promptViewModel.showingError @@ -546,7 +545,7 @@ constructor( } AuthType.Coex -> combine( - promptSelectorInteractor.sensorType, + promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, promptViewModel.isPendingConfirmation, @@ -606,7 +605,7 @@ constructor( AuthType.Fingerprint, AuthType.Coex -> combine( - promptSelectorInteractor.sensorType, + promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, promptViewModel.showingError @@ -642,7 +641,7 @@ constructor( AuthType.Fingerprint, AuthType.Coex -> combine( - promptSelectorInteractor.sensorType, + promptSelectorInteractor.fingerprintSensorType, displayStateInteractor.currentRotation ) { sensorType: FingerprintSensorType, rotation: DisplayRotation -> when (sensorType) { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt index 7d3075a9dd74..ed931bd4e66a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt @@ -43,10 +43,11 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -68,7 +69,12 @@ constructor( mobileConnectionsRepository: MobileConnectionsRepository, ) { val subId: StateFlow<Int> = repository.subscriptionId - val isAnySimSecure: Flow<Boolean> = mobileConnectionsRepository.isAnySimSecure + val isAnySimSecure: StateFlow<Boolean> = + mobileConnectionsRepository.isAnySimSecure.stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = mobileConnectionsRepository.getIsAnySimSecure(), + ) val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java b/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java index 09bf04cb6d59..9cb26f35fa66 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java @@ -20,9 +20,10 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.util.time.SystemClock; -import java.util.ArrayList; +import com.google.common.collect.Sets; + import java.util.Collection; -import java.util.List; +import java.util.Set; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; @@ -52,7 +53,7 @@ public class HistoryTracker { private final SystemClock mSystemClock; DelayQueue<CombinedResult> mResults = new DelayQueue<>(); - private final List<BeliefListener> mBeliefListeners = new ArrayList<>(); + private final Set<BeliefListener> mBeliefListeners = Sets.newConcurrentHashSet(); @Inject HistoryTracker(SystemClock systemClock) { @@ -161,11 +162,15 @@ public class HistoryTracker { } void addBeliefListener(BeliefListener listener) { - mBeliefListeners.add(listener); + if (listener != null) { + mBeliefListeners.add(listener); + } } void removeBeliefListener(BeliefListener listener) { - mBeliefListeners.remove(listener); + if (listener != null) { + mBeliefListeners.remove(listener); + } } /** * Represents a falsing score combing all the classifiers together. diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt index 108e22bc392b..64dedea3e6d4 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt @@ -16,6 +16,7 @@ package com.android.systemui.common.ui.binder +import android.view.View import android.widget.ImageView import com.android.systemui.common.shared.model.Icon @@ -30,4 +31,13 @@ object IconViewBinder { is Icon.Resource -> view.setImageResource(icon.res) } } + + fun bindNullable(icon: Icon?, view: ImageView) { + if (icon != null) { + view.visibility = View.VISIBLE + bind(icon, view) + } else { + view.visibility = View.GONE + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 88c3f9f6af2e..e31f1adb4921 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal import android.provider.Settings import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -91,8 +92,8 @@ constructor( keyguardTransitionInteractor.startedKeyguardTransitionStep .mapLatest(::determineSceneAfterTransition) .filterNotNull() - .onEach { nextScene -> - communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade) + .onEach { (nextScene, nextTransition) -> + communalSceneInteractor.changeScene(nextScene, nextTransition) } .launchIn(applicationScope) @@ -188,7 +189,7 @@ constructor( private suspend fun determineSceneAfterTransition( lastStartedTransition: TransitionStep, - ): SceneKey? { + ): Pair<SceneKey, TransitionKey>? { val to = lastStartedTransition.to val from = lastStartedTransition.from val docked = dockManager.isDocked @@ -201,22 +202,27 @@ constructor( // underneath the hub is shown. When launching activities over lockscreen, we only // change scenes once the activity launch animation is finished, so avoid // changing the scene here. - CommunalScenes.Blank + Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) } to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> { // When transitioning to the hub from an occluded state, fade out the hub without // doing any translation. - CommunalScenes.Communal + Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade) } // Transitioning to Blank scene when entering the edit mode will be handled separately // with custom animations. to == KeyguardState.GONE && !communalInteractor.editModeOpen.value -> - CommunalScenes.Blank + Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) !docked && !KeyguardState.deviceIsAwakeInState(to) -> { // If the user taps the screen and wakes the device within this timeout, we don't // want to dismiss the hub delay(AWAKE_DEBOUNCE_DELAY) - CommunalScenes.Blank + Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) + } + from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> { + // Make sure the communal hub is showing (immediately, not fading in) when + // transitioning from dozing to hub. + Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately) } else -> null } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt index b27fcfc2f3a7..d8067b887c67 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt @@ -27,26 +27,17 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog -import com.android.systemui.log.dagger.CommunalTableLog -import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.settings.UserFileManager -import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.SharedPreferencesExt.observe import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext /** @@ -56,10 +47,16 @@ import kotlinx.coroutines.withContext interface CommunalPrefsRepository { /** Whether the CTA tile has been dismissed. */ - val isCtaDismissed: Flow<Boolean> + fun isCtaDismissed(user: UserInfo): Flow<Boolean> + + /** Whether the lock screen widget disclaimer has been dismissed by the user. */ + fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> /** Save the CTA tile dismissed state for the current user. */ - suspend fun setCtaDismissedForCurrentUser() + suspend fun setCtaDismissed(user: UserInfo) + + /** Save the lock screen widget disclaimer dismissed state for the current user. */ + suspend fun setDisclaimerDismissed(user: UserInfo) } @OptIn(ExperimentalCoroutinesApi::class) @@ -67,75 +64,43 @@ interface CommunalPrefsRepository { class CommunalPrefsRepositoryImpl @Inject constructor( - @Background private val backgroundScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, - private val userRepository: UserRepository, private val userFileManager: UserFileManager, broadcastDispatcher: BroadcastDispatcher, @CommunalLog logBuffer: LogBuffer, - @CommunalTableLog tableLogBuffer: TableLogBuffer, ) : CommunalPrefsRepository { + private val logger by lazy { Logger(logBuffer, TAG) } - private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl") + override fun isCtaDismissed(user: UserInfo): Flow<Boolean> = + readKeyForUser(user, CTA_DISMISSED_STATE) + + override fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> = + readKeyForUser(user, DISCLAIMER_DISMISSED_STATE) /** - * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an - * initial value. + * Emits an event each time a Backup & Restore restoration job is completed, and once at the + * start of collection. */ private val backupRestorationEvents: Flow<Unit> = - broadcastDispatcher.broadcastFlow( - filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), - flags = Context.RECEIVER_NOT_EXPORTED, - permission = BackupHelper.PERMISSION_SELF, - ) - - override val isCtaDismissed: Flow<Boolean> = - combine( - userRepository.selectedUserInfo, - // Make sure combine can emit even if we never get a Backup & Restore event, - // which is the most common case as restoration only happens on initial device - // setup. - backupRestorationEvents.emitOnStart().onEach { - logger.i("Restored state for communal preferences.") - }, - ) { user, _ -> - user - } - .flatMapLatest(::observeCtaDismissState) - .logDiffsForTable( - tableLogBuffer = tableLogBuffer, - columnPrefix = "", - columnName = "isCtaDismissed", - initialValue = false, - ) - .stateIn( - scope = backgroundScope, - started = SharingStarted.WhileSubscribed(), - initialValue = false, + broadcastDispatcher + .broadcastFlow( + filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), + flags = Context.RECEIVER_NOT_EXPORTED, + permission = BackupHelper.PERMISSION_SELF, ) + .onEach { logger.i("Restored state for communal preferences.") } + .emitOnStart() - override suspend fun setCtaDismissedForCurrentUser() = + override suspend fun setCtaDismissed(user: UserInfo) = withContext(bgDispatcher) { - getSharedPrefsForUser(userRepository.getSelectedUserInfo()) - .edit() - .putBoolean(CTA_DISMISSED_STATE, true) - .apply() - + getSharedPrefsForUser(user).edit().putBoolean(CTA_DISMISSED_STATE, true).apply() logger.i("Dismissed CTA tile") } - private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> = - getSharedPrefsForUser(user) - .observe() - // Emit at the start of collection to ensure we get an initial value - .onStart { emit(Unit) } - .map { getCtaDismissedState() } - .flowOn(bgDispatcher) - - private suspend fun getCtaDismissedState(): Boolean = + override suspend fun setDisclaimerDismissed(user: UserInfo) = withContext(bgDispatcher) { - getSharedPrefsForUser(userRepository.getSelectedUserInfo()) - .getBoolean(CTA_DISMISSED_STATE, false) + getSharedPrefsForUser(user).edit().putBoolean(DISCLAIMER_DISMISSED_STATE, true).apply() + logger.i("Dismissed widget disclaimer") } private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences { @@ -146,9 +111,19 @@ constructor( ) } + private fun readKeyForUser(user: UserInfo, key: String): Flow<Boolean> { + return backupRestorationEvents + .flatMapLatest { + val sharedPrefs = getSharedPrefsForUser(user) + sharedPrefs.observe().emitOnStart().map { sharedPrefs.getBoolean(key, false) } + } + .flowOn(bgDispatcher) + } + companion object { - const val TAG = "CommunalRepository" + const val TAG = "CommunalPrefsRepository" const val FILE_NAME = "communal_hub_prefs" const val CTA_DISMISSED_STATE = "cta_dismissed" + const val DISCLAIMER_DISMISSED_STATE = "disclaimer_dismissed" } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 00678a81b572..9f3ade9cd425 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -28,7 +28,6 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.repository.CommunalMediaRepository -import com.android.systemui.communal.data.repository.CommunalPrefsRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent @@ -99,7 +98,7 @@ constructor( @Background val bgDispatcher: CoroutineDispatcher, broadcastDispatcher: BroadcastDispatcher, private val widgetRepository: CommunalWidgetRepository, - private val communalPrefsRepository: CommunalPrefsRepository, + private val communalPrefsInteractor: CommunalPrefsInteractor, private val mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, keyguardInteractor: KeyguardInteractor, @@ -325,7 +324,7 @@ constructor( } /** Dismiss the CTA tile from the hub in view mode. */ - suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser() + suspend fun dismissCtaTile() = communalPrefsInteractor.setCtaDismissed() /** Add a widget at the specified position. */ fun addWidget( @@ -461,7 +460,7 @@ constructor( /** CTA tile to be displayed in the glanceable hub (view mode). */ val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> = - communalPrefsRepository.isCtaDismissed.map { isDismissed -> + communalPrefsInteractor.isCtaDismissed.map { isDismissed -> if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode()) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt new file mode 100644 index 000000000000..3517650c8cf3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.communal.domain.interactor + +import android.content.pm.UserInfo +import com.android.app.tracing.coroutines.launch +import com.android.systemui.communal.data.repository.CommunalPrefsRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.dagger.CommunalTableLog +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.settings.UserTracker +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.stateIn + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalPrefsInteractor +@Inject +constructor( + @Background private val bgScope: CoroutineScope, + private val repository: CommunalPrefsRepository, + userInteractor: SelectedUserInteractor, + private val userTracker: UserTracker, + @CommunalTableLog tableLogBuffer: TableLogBuffer +) { + + val isCtaDismissed: Flow<Boolean> = + userInteractor.selectedUserInfo + .flatMapLatest { user -> repository.isCtaDismissed(user) } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnPrefix = "", + columnName = "isCtaDismissed", + initialValue = false, + ) + .stateIn( + scope = bgScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + suspend fun setCtaDismissed(user: UserInfo = userTracker.userInfo) = + repository.setCtaDismissed(user) + + val isDisclaimerDismissed: Flow<Boolean> = + userInteractor.selectedUserInfo + .flatMapLatest { user -> repository.isDisclaimerDismissed(user) } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnPrefix = "", + columnName = "isDisclaimerDismissed", + initialValue = false, + ) + .stateIn( + scope = bgScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + fun setDisclaimerDismissed(user: UserInfo = userTracker.userInfo) { + bgScope.launch("$TAG#setDisclaimerDismissed") { repository.setDisclaimerDismissed(user) } + } + + private companion object { + const val TAG = "CommunalPrefsInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index 20d8a2a191cf..fd540c4b6708 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -22,6 +22,7 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.data.repository.CommunalSceneRepository import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -60,14 +61,14 @@ constructor( communalSceneRepository.snapToScene(newScene, delayMillis) } - /** Immediately snaps to the new scene when activity is started. */ - fun snapToSceneForActivityStart(newScene: SceneKey, delayMillis: Long = 0) { + /** Changes to Blank scene when starting an activity after dismissing keyguard. */ + fun changeSceneForActivityStartOnDismissKeyguard() { // skip if we're starting edit mode activity, as it will be handled later by changeScene // with transition key [CommunalTransitionKeys.ToEditMode]. if (_editModeState.value == EditModeState.STARTING) { return } - snapToScene(newScene, delayMillis) + changeScene(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) } /** @@ -144,8 +145,14 @@ constructor( * * This flow will be true during any transition and when idle on the communal scene. */ - val isCommunalVisible: Flow<Boolean> = - transitionState.map { - !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) - } + val isCommunalVisible: StateFlow<Boolean> = + transitionState + .map { + !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index c0c586191834..9185384e79a3 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -26,6 +26,7 @@ import androidx.activity.result.ActivityResultLauncher import com.android.internal.logging.UiEventLogger import com.android.systemui.Flags.enableWidgetPickerSizeFilter import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.domain.model.CommunalContentModel @@ -42,6 +43,7 @@ import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.dagger.MediaModule import com.android.systemui.res.R import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.not import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.CoroutineDispatcher @@ -67,6 +69,7 @@ constructor( private val uiEventLogger: UiEventLogger, @CommunalLog logBuffer: LogBuffer, @Background private val backgroundDispatcher: CoroutineDispatcher, + private val communalPrefsInteractor: CommunalPrefsInteractor, ) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) { private val logger = Logger(logBuffer, "CommunalEditModeViewModel") @@ -76,9 +79,16 @@ constructor( override val isCommunalContentVisible: Flow<Boolean> = communalSceneInteractor.editModeState.map { it == EditModeState.SHOWING } + val showDisclaimer: Flow<Boolean> = + allOf(isCommunalContentVisible, not(communalPrefsInteractor.isDisclaimerDismissed)) + + fun onDisclaimerDismissed() { + communalPrefsInteractor.setDisclaimerDismissed() + } + /** - * Emits when edit mode activity can show, after we've transitioned to [KeyguardState.GONE] - * and edit mode is open. + * Emits when edit mode activity can show, after we've transitioned to [KeyguardState.GONE] and + * edit mode is open. */ val canShowEditMode = allOf( diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 40df6cecaeb8..46f802fd2bce 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -96,7 +96,8 @@ constructor( run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") } } } - } ?: run { Log.w(TAG, "No data in result.") } + } + ?: run { Log.w(TAG, "No data in result.") } } else -> Log.w( @@ -127,7 +128,7 @@ constructor( Box( modifier = Modifier.fillMaxSize() - .background(LocalAndroidColorScheme.current.outlineVariant), + .background(LocalAndroidColorScheme.current.onSecondaryFixed), ) { CommunalHub( viewModel = communalViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepository.kt new file mode 100644 index 000000000000..250b432fd71c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepository.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.deviceconfig.data.repository + +import android.provider.DeviceConfig +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.DeviceConfigProxy +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn + +@SysUISingleton +class DeviceConfigRepository +@Inject +constructor( + @Background private val backgroundExecutor: Executor, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val dataSource: DeviceConfigProxy, +) { + + fun property(namespace: String, name: String, default: Boolean): Flow<Boolean> { + return conflatedCallbackFlow { + val listener = { properties: DeviceConfig.Properties -> + if (properties.keyset.contains(name)) { + trySend(properties.getBoolean(name, default)) + } + } + + dataSource.addOnPropertiesChangedListener( + namespace, + backgroundExecutor, + listener, + ) + trySend(dataSource.getBoolean(namespace, name, default)) + + awaitClose { dataSource.removeOnPropertiesChangedListener(listener) } + } + .flowOn(backgroundDispatcher) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractor.kt new file mode 100644 index 000000000000..d04f8bcaf9ff --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractor.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.deviceconfig.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceconfig.data.repository.DeviceConfigRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class DeviceConfigInteractor +@Inject +constructor( + private val repository: DeviceConfigRepository, +) { + + fun property(namespace: String, name: String, default: Boolean): Flow<Boolean> { + return repository.property(namespace, name, default) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 9d6c2bf4c1d1..0bcfa9616b76 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -47,7 +47,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.scan import kotlinx.coroutines.flow.shareIn @@ -155,10 +154,11 @@ constructor( is DisplayEvent.Changed -> previousIds + id } } - .shareIn( + .distinctUntilChanged() + .stateIn( bgApplicationScope, - started = SharingStarted.WhileSubscribed(), - replay = 1 + SharingStarted.WhileSubscribed(), + emptySet(), ) } else { oldEnabledDisplays.map { enabledDisplaysSet -> @@ -177,7 +177,16 @@ constructor( enabledDisplayIds .mapElementsLazily { displayId -> getDisplay(displayId) } .flowOn(backgroundCoroutineDispatcher) - .debugLog("enabledDisplayIds") + .debugLog("enabledDisplays") + .stateIn( + bgApplicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = + setOf( + getDisplay(Display.DEFAULT_DISPLAY) + ?: error("Unable to get default display.") + ) + ) } else { oldEnabledDisplays } @@ -355,20 +364,31 @@ constructor( * [createValue] returns a null element, it will not be added in the output set. */ private fun <T, V> Flow<Set<T>>.mapElementsLazily(createValue: (T) -> V?): Flow<Set<V>> { - var initialSet = emptySet<T>() - val currentMap = mutableMapOf<T, V>() - var resultSet = emptySet<V>() - return onEach { currentSet -> - val removed = initialSet - currentSet - val added = currentSet - initialSet - if (added.isNotEmpty() || removed.isNotEmpty()) { - added.forEach { key: T -> createValue(key)?.let { currentMap[key] = it } } - removed.forEach { key: T -> currentMap.remove(key) } - resultSet = currentMap.values.toSet() // Creates a **copy** of values + data class State<T, V>( + val previousSet: Set<T>, + // Caches T values from the previousSet that were already converted to V + val valueMap: Map<T, V>, + val resultSet: Set<V> + ) + + val initialState = State(emptySet<T>(), emptyMap(), emptySet<V>()) + + return this.scan(initialState) { state, currentSet -> + if (currentSet == state.previousSet) { + state + } else { + val removed = state.previousSet - currentSet + val added = currentSet - state.previousSet + val newMap = state.valueMap.toMutableMap() + + added.forEach { key -> createValue(key)?.let { newMap[key] = it } } + removed.forEach { key -> newMap.remove(key) } + + val resultSet = newMap.values.toSet() + State(currentSet, newMap, resultSet) } - initialSet = currentSet } - .map { resultSet } + .map { it.resultSet } } private companion object { diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt index 73b7a8ac7bd3..e4b290d7d10e 100644 --- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt @@ -85,7 +85,7 @@ interface ConnectedDisplayInteractor { class ConnectedDisplayInteractorImpl @Inject constructor( - private val virtualDeviceManager: VirtualDeviceManager, + private val virtualDeviceManager: VirtualDeviceManager?, keyguardRepository: KeyguardRepository, displayRepository: DisplayRepository, deviceStateRepository: DeviceStateRepository, @@ -156,6 +156,7 @@ constructor( private fun isVirtualDeviceOwnedMirrorDisplay(display: Display): Boolean { return Flags.interactiveScreenMirror() && + virtualDeviceManager != null && virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(display.displayId) } } diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt index be4c90356a3d..7b5139aa510e 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt @@ -97,14 +97,15 @@ constructor( State.IDLE -> { setState(State.TIMEOUT_WAIT) } - State.RUNNING_BACKWARDS -> callback?.onCancelAnimator() + State.RUNNING_BACKWARDS_FROM_UP, + State.RUNNING_BACKWARDS_FROM_CANCEL -> callback?.onCancelAnimator() else -> {} } } fun handleActionUp() { if (state == State.RUNNING_FORWARD) { - setState(State.RUNNING_BACKWARDS) + setState(State.RUNNING_BACKWARDS_FROM_UP) callback?.onReverseAnimator() } } @@ -113,7 +114,7 @@ constructor( when (state) { State.TIMEOUT_WAIT -> setState(State.IDLE) State.RUNNING_FORWARD -> { - setState(State.RUNNING_BACKWARDS) + setState(State.RUNNING_BACKWARDS_FROM_CANCEL) callback?.onReverseAnimator() } else -> {} @@ -127,20 +128,24 @@ constructor( /** This function is called both when an animator completes or gets cancelled */ fun handleAnimationComplete() { - if (state == State.RUNNING_FORWARD) { - setState(State.IDLE) - vibrate(snapEffect) - if (keyguardStateController.isUnlocked) { - callback?.onPrepareForLaunch() - qsTile?.longClick(expandable) - } else { - callback?.onResetProperties() - qsTile?.longClick(expandable) + when (state) { + State.RUNNING_FORWARD -> { + setState(State.IDLE) + vibrate(snapEffect) + if (keyguardStateController.isUnlocked) { + qsTile?.longClick(expandable) + } else { + callback?.onResetProperties() + qsTile?.longClick(expandable) + } } - } - if (state != State.TIMEOUT_WAIT) { - // This will happen if the animator did not finish by being cancelled - setState(State.IDLE) + State.RUNNING_BACKWARDS_FROM_UP -> { + setState(State.IDLE) + callback?.onEffectFinishedReversing() + qsTile?.click(expandable) + } + State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE) + else -> {} } } @@ -191,20 +196,23 @@ constructor( enum class State { IDLE, /* The effect is idle waiting for touch input */ - TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */ + TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */ RUNNING_FORWARD, /* The effect is running normally */ - RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */ + /* The effect was interrupted by an ACTION_UP and is now running backwards */ + RUNNING_BACKWARDS_FROM_UP, + /* The effect was interrupted by an ACTION_CANCEL and is now running backwards */ + RUNNING_BACKWARDS_FROM_CANCEL, } /** Callbacks to notify view and animator actions */ interface Callback { - /** Prepare for an activity launch */ - fun onPrepareForLaunch() - /** Reset the tile visual properties */ fun onResetProperties() + /** Event where the effect completed by being reversed */ + fun onEffectFinishedReversing() + /** Start the effect animator */ fun onStartAnimator() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt new file mode 100644 index 000000000000..34b10c727cd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyboard.shortcut.data.source + +import android.content.res.Resources +import android.view.KeyEvent.KEYCODE_DPAD_LEFT +import android.view.KeyEvent.KEYCODE_DPAD_RIGHT +import android.view.KeyEvent.KEYCODE_DPAD_UP +import android.view.KeyEvent.KEYCODE_TAB +import android.view.KeyEvent.META_ALT_ON +import android.view.KeyEvent.META_CTRL_ON +import android.view.KeyEvent.META_META_ON +import android.view.KeyEvent.META_SHIFT_ON +import com.android.systemui.keyboard.shortcut.shared.model.shortcut +import com.android.systemui.res.R +import javax.inject.Inject + +class MultitaskingShortcutsSource @Inject constructor(private val resources: Resources) { + + fun splitScreenShortcuts() = + listOf( + // Enter Split screen with current app to RHS: + // - Meta + Ctrl + Right arrow + shortcut(resources.getString(R.string.system_multitasking_rhs)) { + command(META_META_ON, META_CTRL_ON, KEYCODE_DPAD_RIGHT) + }, + // Enter Split screen with current app to LHS: + // - Meta + Ctrl + Left arrow + shortcut(resources.getString(R.string.system_multitasking_lhs)) { + command(META_META_ON, META_CTRL_ON, KEYCODE_DPAD_LEFT) + }, + // Switch from Split screen to full screen: + // - Meta + Ctrl + Up arrow + shortcut(resources.getString(R.string.system_multitasking_full_screen)) { + command(META_META_ON, META_CTRL_ON, KEYCODE_DPAD_UP) + }, + // Change split screen focus to RHS: + // - Meta + Alt + Right arrow + shortcut(resources.getString(R.string.system_multitasking_splitscreen_focus_rhs)) { + command(META_META_ON, META_ALT_ON, KEYCODE_DPAD_RIGHT) + }, + // Change split screen focus to LHS: + // - Meta + Alt + Left arrow + shortcut(resources.getString(R.string.system_multitasking_splitscreen_focus_rhs)) { + command(META_META_ON, META_ALT_ON, KEYCODE_DPAD_LEFT) + }, + ) + + fun recentsShortcuts() = + listOf( + // Cycle through recent apps (forward): + // - Alt + Tab + shortcut(resources.getString(R.string.group_system_cycle_forward)) { + command(META_ALT_ON, KEYCODE_TAB) + }, + // Cycle through recent apps (back): + // - Shift + Alt + Tab + shortcut(resources.getString(R.string.group_system_cycle_back)) { + command(META_SHIFT_ON, META_ALT_ON, KEYCODE_TAB) + }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt new file mode 100644 index 000000000000..a4304e5d1d33 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyboard.shortcut.data.source + +import android.content.res.Resources +import android.view.KeyEvent.KEYCODE_A +import android.view.KeyEvent.KEYCODE_DEL +import android.view.KeyEvent.KEYCODE_DPAD_LEFT +import android.view.KeyEvent.KEYCODE_ENTER +import android.view.KeyEvent.KEYCODE_ESCAPE +import android.view.KeyEvent.KEYCODE_H +import android.view.KeyEvent.KEYCODE_I +import android.view.KeyEvent.KEYCODE_L +import android.view.KeyEvent.KEYCODE_N +import android.view.KeyEvent.KEYCODE_S +import android.view.KeyEvent.KEYCODE_SLASH +import android.view.KeyEvent.KEYCODE_TAB +import android.view.KeyEvent.META_CTRL_ON +import android.view.KeyEvent.META_META_ON +import com.android.systemui.keyboard.shortcut.shared.model.shortcut +import com.android.systemui.res.R +import javax.inject.Inject + +class SystemShortcutsSource @Inject constructor(private val resources: Resources) { + + fun generalShortcuts() = + listOf( + // Access list of all apps and search (i.e. Search/Launcher): + // - Meta + shortcut(resources.getString(R.string.group_system_access_all_apps_search)) { + command(META_META_ON) + }, + // Access home screen: + // - Meta + H + // - Meta + Enter + shortcut(resources.getString(R.string.group_system_access_home_screen)) { + command(META_META_ON, KEYCODE_H) + command(META_META_ON, KEYCODE_ENTER) + }, + // Overview of open apps: + // - Meta + Tab + shortcut(resources.getString(R.string.group_system_overview_open_apps)) { + command(META_META_ON, KEYCODE_TAB) + }, + // Back: go back to previous state (back button) + // - Meta + Escape OR + // - Meta + Backspace OR + // - Meta + Left arrow + shortcut(resources.getString(R.string.group_system_go_back)) { + command(META_META_ON, KEYCODE_ESCAPE) + command(META_META_ON, KEYCODE_DEL) + command(META_META_ON, KEYCODE_DPAD_LEFT) + }, + // Take a full screenshot: + // - Meta + Ctrl + S + shortcut(resources.getString(R.string.group_system_full_screenshot)) { + command(META_META_ON, META_CTRL_ON, KEYCODE_S) + }, + // Access list of system / apps shortcuts: + // - Meta + / + shortcut(resources.getString(R.string.group_system_access_system_app_shortcuts)) { + command(META_META_ON, KEYCODE_SLASH) + }, + // Access notification shade: + // - Meta + N + shortcut(resources.getString(R.string.group_system_access_notification_shade)) { + command(META_META_ON, KEYCODE_N) + }, + // Lock screen: + // - Meta + L + shortcut(resources.getString(R.string.group_system_lock_screen)) { + command(META_META_ON, KEYCODE_L) + }, + ) + + fun systemAppsShortcuts() = + listOf( + // Pull up Notes app for quick memo: + // - Meta + Ctrl + N + shortcut(resources.getString(R.string.group_system_quick_memo)) { + command(META_META_ON, META_CTRL_ON, KEYCODE_N) + }, + // Access system settings: + // - Meta + I + shortcut(resources.getString(R.string.group_system_access_system_settings)) { + command(META_META_ON, KEYCODE_I) + }, + // Access Assistant: + // - Meta + A + shortcut(resources.getString(R.string.group_system_access_google_assistant)) { + command(META_META_ON, KEYCODE_A) + }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt new file mode 100644 index 000000000000..ea90b18c06b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyboard.shortcut.shared.model + +import android.graphics.drawable.Icon + +data class Shortcut(val label: String, val icon: Icon? = null, val commands: List<ShortcutCommand>) + +class ShortcutBuilder(private val label: String, private val icon: Icon? = null) { + val commands = mutableListOf<ShortcutCommand>() + + fun command(vararg keyCodes: Int) { + commands += ShortcutCommand(keyCodes.toList()) + } + + fun build() = Shortcut(label, icon, commands) +} + +fun shortcut(label: String, icon: Icon? = null, block: ShortcutBuilder.() -> Unit): Shortcut = + ShortcutBuilder(label).apply(block).build() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt new file mode 100644 index 000000000000..747efbaf4838 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyboard.shortcut.shared.model + +class ShortcutCommand(val keyCodes: List<Int>) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt index 04fa74974afc..8706280df975 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt @@ -123,7 +123,7 @@ constructor( resources.getFloat(R.dimen.shortcut_helper_screen_width_fraction) // maxWidth needs to be set before the sheet is drawn, otherwise the call will have no // effect. - val screenWidth = resources.displayMetrics.widthPixels + val screenWidth = windowManager.maximumWindowMetrics.bounds.width() bottomSheetBehavior.maxWidth = (sheetScreenWidthFraction * screenWidth).toInt() } @@ -132,7 +132,7 @@ constructor( val safeDrawingInsets = insets.safeDrawing // Make sure the bottom sheet is not covered by the status bar. bottomSheetBehavior.maxHeight = - resources.displayMetrics.heightPixels - safeDrawingInsets.top + windowManager.maximumWindowMetrics.bounds.height() - safeDrawingInsets.top // Make sure the contents inside of the bottom sheet are not hidden by system bars, or // cutouts. bottomSheet.updatePadding( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 209bc7adac33..c4b70d8013bf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -89,6 +89,7 @@ import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibil import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.ScreenPowerState; import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartable; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.DisplayTracker; @@ -122,6 +123,7 @@ public class KeyguardService extends Service { private final KeyguardInteractor mKeyguardInteractor; private final Lazy<SceneInteractor> mSceneInteractorLazy; private final Executor mMainExecutor; + private final Lazy<KeyguardStateCallbackStartable> mKeyguardStateCallbackStartableLazy; private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers, SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap, @@ -341,7 +343,8 @@ public class KeyguardService extends Service { Lazy<SceneInteractor> sceneInteractorLazy, @Main Executor mainExecutor, KeyguardInteractor keyguardInteractor, - KeyguardEnabledInteractor keyguardEnabledInteractor) { + KeyguardEnabledInteractor keyguardEnabledInteractor, + Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; @@ -353,6 +356,7 @@ public class KeyguardService extends Service { mKeyguardInteractor = keyguardInteractor; mSceneInteractorLazy = sceneInteractorLazy; mMainExecutor = mainExecutor; + mKeyguardStateCallbackStartableLazy = keyguardStateCallbackStartableLazy; if (KeyguardWmStateRefactor.isEnabled()) { WindowManagerLockscreenVisibilityViewBinder.bind( @@ -440,7 +444,11 @@ public class KeyguardService extends Service { public void addStateMonitorCallback(IKeyguardStateCallback callback) { trace("addStateMonitorCallback"); checkPermission(); - mKeyguardViewMediator.addStateMonitorCallback(callback); + if (SceneContainerFlag.isEnabled()) { + mKeyguardStateCallbackStartableLazy.get().addCallback(callback); + } else { + mKeyguardViewMediator.addStateMonitorCallback(callback); + } } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 00566c1b04e3..a7e263320745 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -39,7 +39,7 @@ import com.android.app.animation.Interpolators import com.android.internal.R import com.android.keyguard.KeyguardClockSwitchController import com.android.keyguard.KeyguardViewController -import com.android.systemui.Flags.fastUnlockTransition +import com.android.systemui.Flags.fasterUnlockTransition import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags @@ -363,9 +363,9 @@ class KeyguardUnlockAnimationController @Inject constructor( } with(wallpaperCannedUnlockAnimator) { - duration = if (fastUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS + duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS else LAUNCHER_ICONS_ANIMATION_DURATION_MS - interpolator = if (fastUnlockTransition()) Interpolators.LINEAR + interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR else Interpolators.ALPHA_OUT addUpdateListener { valueAnimator: ValueAnimator -> setWallpaperAppearAmount(valueAnimator.animatedValue as Float) @@ -613,7 +613,7 @@ class KeyguardUnlockAnimationController @Inject constructor( val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock && biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM - val duration = if (fastUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS + val duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS else LAUNCHER_ICONS_ANIMATION_DURATION_MS listeners.forEach { it.onUnlockAnimationStarted( @@ -1148,7 +1148,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * TODO (b/298186160) replace references with the constant itself when flag is removed */ private fun cannedUnlockStartDelayMs(): Long { - return if (fastUnlockTransition()) CANNED_UNLOCK_START_DELAY + return if (fasterUnlockTransition()) CANNED_UNLOCK_START_DELAY else LEGACY_CANNED_UNLOCK_START_DELAY } @@ -1157,7 +1157,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * TODO (b/298186160) replace references with the constant itself when flag is removed */ private fun unlockAnimationDurationMs(): Long { - return if (fastUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS + return if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS else LEGACY_UNLOCK_ANIMATION_DURATION_MS } @@ -1166,7 +1166,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * TODO (b/298186160) replace references with the constant itself when flag is removed */ private fun surfaceBehindFadeOutDurationMs(): Long { - return if (fastUnlockTransition()) SURFACE_BEHIND_FADE_OUT_DURATION_MS + return if (fasterUnlockTransition()) SURFACE_BEHIND_FADE_OUT_DURATION_MS else LEGACY_SURFACE_BEHIND_SWIPE_FADE_DURATION_MS } @@ -1175,7 +1175,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * TODO (b/298186160) replace references with the constant itself when flag is removed */ private fun surfaceBehindFadeOutStartDelayMs(): Long { - return if (fastUnlockTransition()) SURFACE_BEHIND_FADE_OUT_START_DELAY_MS + return if (fasterUnlockTransition()) SURFACE_BEHIND_FADE_OUT_START_DELAY_MS else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 2d60fcca58cc..36b7ed26158f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -151,6 +151,7 @@ import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -178,6 +179,8 @@ import com.android.wm.shell.keyguard.KeyguardTransitions; import dagger.Lazy; +import kotlinx.coroutines.CoroutineDispatcher; + import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -187,8 +190,6 @@ import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; -import kotlinx.coroutines.CoroutineDispatcher; - /** * Mediates requests related to the keyguard. This includes queries about the * state of the keyguard, power management events that effect whether the keyguard @@ -3502,12 +3503,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, + " --> flags=0x" + Integer.toHexString(flags)); } - try { - mStatusBarService.disableForUser(flags, mStatusBarDisableToken, - mContext.getPackageName(), - mSelectedUserInteractor.getSelectedUserId(true)); - } catch (RemoteException e) { - Log.d(TAG, "Failed to set disable flags: " + flags, e); + if (!SceneContainerFlag.isEnabled()) { + try { + mStatusBarService.disableForUser(flags, mStatusBarDisableToken, + mContext.getPackageName(), + mSelectedUserInteractor.getSelectedUserId(true)); + } catch (RemoteException e) { + Log.d(TAG, "Failed to set disable flags: " + flags, e); + } } } } @@ -3798,6 +3801,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void notifyDefaultDisplayCallbacks(boolean showing) { + if (SceneContainerFlag.isEnabled()) { + return; + } + // TODO(b/140053364) whitelistIpcs(() -> { int size = mKeyguardStateCallbacks.size(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index 882f2315f6e8..dd3e6190ceaf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -188,35 +188,33 @@ constructor( ) private val isFingerprintEnrolled: Flow<Boolean> = - selectedUserId - .flatMapLatest { currentUserId -> - conflatedCallbackFlow { - val callback = - object : AuthController.Callback { - override fun onEnrollmentsChanged( - sensorBiometricType: BiometricType, - userId: Int, - hasEnrollments: Boolean - ) { - if (sensorBiometricType.isFingerprint && userId == currentUserId) { - trySendWithFailureLogging( - hasEnrollments, - TAG, - "update fpEnrollment" - ) - } + selectedUserId.flatMapLatest { currentUserId -> + conflatedCallbackFlow { + val callback = + object : AuthController.Callback { + override fun onEnrollmentsChanged( + sensorBiometricType: BiometricType, + userId: Int, + hasEnrollments: Boolean + ) { + if (sensorBiometricType.isFingerprint && userId == currentUserId) { + trySendWithFailureLogging( + hasEnrollments, + TAG, + "update fpEnrollment" + ) } } - authController.addCallback(callback) - awaitClose { authController.removeCallback(callback) } - } + } + authController.addCallback(callback) + trySendWithFailureLogging( + authController.isFingerprintEnrolled(currentUserId), + TAG, + "Initial value of fingerprint enrollment" + ) + awaitClose { authController.removeCallback(callback) } } - .stateIn( - scope, - started = SharingStarted.Eagerly, - initialValue = - authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id) - ) + } private val isFaceEnrolled: Flow<Boolean> = selectedUserId.flatMapLatest { selectedUserId: Int -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index d508b2bc11e8..cdf3b06d41b8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -78,6 +78,8 @@ interface KeyguardRepository { val keyguardAlpha: StateFlow<Float> + val panelAlpha: MutableStateFlow<Float> + /** * Observable for whether the keyguard is showing. * @@ -250,6 +252,9 @@ interface KeyguardRepository { /** Sets the current amount of alpha that should be used for rendering the keyguard. */ fun setKeyguardAlpha(alpha: Float) + /** Temporary shim for fading out content when the brightness slider is used */ + fun setPanelAlpha(alpha: Float) + /** Whether the device is actively dreaming */ fun setDreaming(isDreaming: Boolean) @@ -338,6 +343,8 @@ constructor( private val _keyguardAlpha = MutableStateFlow(1f) override val keyguardAlpha = _keyguardAlpha.asStateFlow() + override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f) + private val _clockShouldBeCentered = MutableStateFlow(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow() @@ -659,6 +666,10 @@ constructor( _keyguardAlpha.value = alpha } + override fun setPanelAlpha(alpha: Float) { + panelAlpha.value = alpha + } + override fun setDreaming(isDreaming: Boolean) { this.isDreaming.value = isDreaming } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt index 6522439460e4..bd5d096b4f6a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt @@ -23,11 +23,13 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.shared.model.ActiveUnlockModel import com.android.systemui.keyguard.shared.model.TrustManagedModel import com.android.systemui.keyguard.shared.model.TrustModel import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext /** Encapsulates any state relevant to trust agents and trust grants. */ interface TrustRepository { @@ -51,7 +54,7 @@ interface TrustRepository { val isCurrentUserTrustUsuallyManaged: StateFlow<Boolean> /** Flow representing whether the current user is trusted. */ - val isCurrentUserTrusted: Flow<Boolean> + val isCurrentUserTrusted: StateFlow<Boolean> /** Flow representing whether active unlock is running for the current user. */ val isCurrentUserActiveUnlockRunning: Flow<Boolean> @@ -63,6 +66,9 @@ interface TrustRepository { /** A trust agent is requesting to dismiss the keyguard from a trust change. */ val trustAgentRequestingToDismissKeyguard: Flow<TrustModel> + + /** Reports a keyguard visibility change. */ + suspend fun reportKeyguardShowingChanged() } @OptIn(ExperimentalCoroutinesApi::class) @@ -71,6 +77,7 @@ class TrustRepositoryImpl @Inject constructor( @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, private val trustManager: TrustManager, private val logger: TrustRepositoryLogger, @@ -191,11 +198,26 @@ constructor( private fun isUserTrustManaged(userId: Int) = trustManagedForUser[userId]?.isTrustManaged ?: false - override val isCurrentUserTrusted: Flow<Boolean> + override val isCurrentUserTrusted: StateFlow<Boolean> get() = combine(trust, userRepository.selectedUserInfo, ::Pair) - .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false } + .map { isCurrentUserTrusted(it.second.id) } .distinctUntilChanged() .onEach { logger.isCurrentUserTrusted(it) } .onStart { emit(false) } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = isCurrentUserTrusted(), + ) + + private fun isCurrentUserTrusted( + selectedUserId: Int = userRepository.getSelectedUserInfo().id + ): Boolean { + return latestTrustModelForUser[selectedUserId]?.isTrusted ?: false + } + + override suspend fun reportKeyguardShowingChanged() { + withContext(backgroundDispatcher) { trustManager.reportKeyguardShowingChanged() } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 49d00af61a24..5573f0d5f644 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest @@ -168,7 +169,9 @@ constructor( keyguardInteractor.isKeyguardGoingAway.filter { it }.map {}, // map to Unit keyguardInteractor.isKeyguardOccluded.flatMapLatest { keyguardOccluded -> if (keyguardOccluded) { - primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled + primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled.drop( + 1 + ) // drop the initial state } else { emptyFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index f3692bdd3a4a..2f40c99c69f9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -17,8 +17,10 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator +import android.app.DreamManager import com.android.app.animation.Interpolators import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -51,8 +53,10 @@ constructor( keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, val deviceEntryRepository: DeviceEntryRepository, + private val dreamManager: DreamManager, ) : TransitionInteractor( fromState = KeyguardState.DOZING, @@ -119,7 +123,8 @@ constructor( .filterRelevantKeyguardStateAnd { isAwake -> isAwake } .sample( keyguardInteractor.isKeyguardOccluded, - communalInteractor.isIdleOnCommunal, + communalInteractor.isCommunalAvailable, + communalSceneInteractor.isIdleOnCommunal, canTransitionToGoneOnWake, keyguardInteractor.primaryBouncerShowing, ) @@ -127,6 +132,7 @@ constructor( ( _, occluded, + isCommunalAvailable, isIdleOnCommunal, canTransitionToGoneOnWake, primaryBouncerShowing) -> @@ -141,6 +147,10 @@ constructor( KeyguardState.OCCLUDED } else if (isIdleOnCommunal) { KeyguardState.GLANCEABLE_HUB + } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) { + // This case handles tapping the power button to transition through + // dream -> off -> hub. + KeyguardState.GLANCEABLE_HUB } else { KeyguardState.LOCKSCREEN } @@ -159,7 +169,8 @@ constructor( powerInteractor.detailedWakefulness .filterRelevantKeyguardStateAnd { it.isAwake() } .sample( - communalInteractor.isIdleOnCommunal, + communalInteractor.isCommunalAvailable, + communalSceneInteractor.isIdleOnCommunal, keyguardInteractor.biometricUnlockState, canTransitionToGoneOnWake, keyguardInteractor.primaryBouncerShowing, @@ -167,6 +178,7 @@ constructor( .collect { ( _, + isCommunalAvailable, isIdleOnCommunal, biometricUnlockState, canDismissLockscreen, @@ -188,6 +200,10 @@ constructor( KeyguardState.PRIMARY_BOUNCER } else if (isIdleOnCommunal) { KeyguardState.GLANCEABLE_HUB + } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) { + // This case handles tapping the power button to transition through + // dream -> off -> hub. + KeyguardState.GLANCEABLE_HUB } else { KeyguardState.LOCKSCREEN }, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index f30eef080ede..35a2d58816b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason.FOLD import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository @@ -368,7 +369,12 @@ constructor( // being delayed in KeyguardViewMediator KeyguardState.DREAMING -> TO_DREAMING_DURATION + 100.milliseconds KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION - KeyguardState.AOD -> TO_AOD_DURATION + KeyguardState.AOD -> + if (powerInteractor.detailedWakefulness.value.lastSleepReason == FOLD) { + TO_AOD_FOLD_DURATION + } else { + TO_AOD_DURATION + } KeyguardState.DOZING -> TO_DOZING_DURATION KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> TO_DREAMING_HOSTED_DURATION KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION @@ -385,6 +391,7 @@ constructor( val TO_DREAMING_HOSTED_DURATION = 933.milliseconds val TO_OCCLUDED_DURATION = 450.milliseconds val TO_AOD_DURATION = 500.milliseconds + val TO_AOD_FOLD_DURATION = 1100.milliseconds val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION val TO_GONE_DURATION = 633.milliseconds val TO_GLANCEABLE_HUB_DURATION = 1.seconds diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt index 8dede01cd20b..9cc0b3cd62e7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -48,6 +49,37 @@ constructor( transitionInteractor: KeyguardTransitionInteractor, ) { + /** + * Whether the keyguard is enabled, per [KeyguardService]. If the keyguard is not enabled, the + * lockscreen cannot be shown and the device will go from AOD/DOZING directly to GONE. + * + * Keyguard can be disabled by selecting Security: "None" in settings, or by apps that hold + * permission to do so (such as Phone). + * + * If the keyguard is disabled while we're locked, we will transition to GONE unless we're in + * lockdown mode. If the keyguard is re-enabled, we'll transition back to LOCKSCREEN if we were + * locked when it was disabled. + */ + val isKeyguardEnabled: StateFlow<Boolean> = repository.isKeyguardEnabled + + /** + * Whether we need to show the keyguard when the keyguard is re-enabled, since we hid it when it + * became disabled. + */ + val showKeyguardWhenReenabled: Flow<Boolean> = + repository.isKeyguardEnabled + // Whenever the keyguard is disabled... + .filter { enabled -> !enabled } + .sampleCombine( + transitionInteractor.currentTransitionInfoInternal, + biometricSettingsRepository.isCurrentUserInLockdown + ) + .map { (_, transitionInfo, inLockdown) -> + // ...we hide the keyguard, if it's showing and we're not in lockdown. In that case, + // we want to remember that and re-show it when keyguard is enabled again. + transitionInfo.to != KeyguardState.GONE && !inLockdown + } + init { /** * Whenever keyguard is disabled, transition to GONE unless we're in lockdown or already @@ -68,24 +100,6 @@ constructor( } } - /** - * Whether we need to show the keyguard when the keyguard is re-enabled, since we hid it when it - * became disabled. - */ - val showKeyguardWhenReenabled: Flow<Boolean> = - repository.isKeyguardEnabled - // Whenever the keyguard is disabled... - .filter { enabled -> !enabled } - .sampleCombine( - transitionInteractor.currentTransitionInfoInternal, - biometricSettingsRepository.isCurrentUserInLockdown - ) - .map { (_, transitionInfo, inLockdown) -> - // ...we hide the keyguard, if it's showing and we're not in lockdown. In that case, - // we want to remember that and re-show it when keyguard is enabled again. - transitionInfo.to != KeyguardState.GONE && !inLockdown - } - fun notifyKeyguardEnabled(enabled: Boolean) { repository.setKeyguardEnabled(enabled) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index ef96be0a98fd..ab1194e66275 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -321,6 +321,10 @@ constructor( @Deprecated("Use the relevant TransitionViewModel") val keyguardAlpha: Flow<Float> = repository.keyguardAlpha + /** Temporary shim for fading out content when the brightness slider is used */ + @Deprecated("SceneContainer uses NotificationStackAppearanceInteractor") + val panelAlpha: StateFlow<Float> = repository.panelAlpha.asStateFlow() + /** * When the lockscreen can be dismissed, emit an alpha value as the user swipes up. This is * useful just before the code commits to moving to GONE. @@ -458,6 +462,10 @@ constructor( repository.setKeyguardAlpha(alpha) } + fun setPanelAlpha(alpha: Float) { + repository.setPanelAlpha(alpha) + } + fun setAnimateDozingTransitions(animate: Boolean) { repository.setAnimateDozingTransitions(animate) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/ToAodFoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/ToAodFoldTransitionInteractor.kt index 6729246dcb7a..21b9e537cc18 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/ToAodFoldTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/ToAodFoldTransitionInteractor.kt @@ -16,30 +16,17 @@ package com.android.systemui.keyguard.domain.interactor -import android.animation.ValueAnimator import android.view.ViewGroup import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.TransitionInfo -import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.shade.ShadeFoldAnimator import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch @SysUISingleton class ToAodFoldTransitionInteractor @Inject constructor( private val keyguardClockInteractor: KeyguardClockInteractor, - private val transitionInteractor: KeyguardTransitionInteractor, - private val transitionRepository: KeyguardTransitionRepository, - @Application private val mainScope: CoroutineScope, - @Main private val mainDispatcher: CoroutineDispatcher, ) { private var parentAnimator: NotificationPanelViewController.ShadeFoldAnimatorImpl? = null @@ -50,7 +37,6 @@ constructor( get() = throw NotImplementedError("Deprecated. Do not call.") override fun prepareFoldToAodAnimation() { - forceToAod() parentAnimator?.prepareFoldToAodAnimation() } @@ -78,21 +64,6 @@ constructor( parentAnimator as? NotificationPanelViewController.ShadeFoldAnimatorImpl? } - /** Forces the keyguard into AOD or Doze */ - private fun forceToAod() { - mainScope.launch(mainDispatcher) { - transitionRepository.startTransition( - TransitionInfo( - "$TAG (Fold transition triggered)", - transitionInteractor.getCurrentState(), - transitionInteractor.asleepKeyguardState.value, - ValueAnimator().apply { duration = 0 }, - TransitionModeOnCanceled.LAST_VALUE, - ) - ) - } - } - companion object { private val TAG = ToAodFoldTransitionInteractor::class.simpleName!! } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt index 2ff6e165293b..73248bbec6ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt @@ -17,14 +17,21 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.TrustRepository import javax.inject.Inject -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch /** Encapsulates any state relevant to trust agents and trust grants. */ @SysUISingleton -class TrustInteractor @Inject constructor(repository: TrustRepository) { +class TrustInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val repository: TrustRepository, +) { /** * Whether the current user has a trust agent enabled. This is true if the user has at least one * trust agent enabled in settings. @@ -39,5 +46,10 @@ class TrustInteractor @Inject constructor(repository: TrustRepository) { val isTrustAgentCurrentlyAllowed: StateFlow<Boolean> = repository.isCurrentUserTrustManaged /** Whether the current user is trusted by any of the enabled trust agents. */ - val isTrusted: Flow<Boolean> = repository.isCurrentUserTrusted + val isTrusted: StateFlow<Boolean> = repository.isCurrentUserTrusted + + /** Reports a keyguard visibility change. */ + fun reportKeyguardShowingChanged() { + applicationScope.launch { repository.reportKeyguardShowingChanged() } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 6550937d2db0..db33acb93dc6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -86,7 +86,10 @@ constructor( privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION + // Avoid announcing window title. + accessibilityTitle = " " } + private var alternateBouncerView: ConstraintLayout? = null override fun start() { @@ -112,13 +115,28 @@ constructor( } private fun removeViewFromWindowManager() { - if (alternateBouncerView == null || !alternateBouncerView!!.isAttachedToWindow) { - return - } + alternateBouncerView?.let { + alternateBouncerView = null + if (it.isAttachedToWindow) { + it.removeOnAttachStateChangeListener(onAttachAddBackGestureHandler) + Log.d(TAG, "Removing alternate bouncer view immediately") + windowManager.get().removeView(it) + } else { + // once the view is attached, remove it + it.addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(view: View) { + it.removeOnAttachStateChangeListener(this) + it.removeOnAttachStateChangeListener(onAttachAddBackGestureHandler) + Log.d(TAG, "Removing alternate bouncer view on attached") + windowManager.get().removeView(it) + } - windowManager.get().removeView(alternateBouncerView) - alternateBouncerView!!.removeOnAttachStateChangeListener(onAttachAddBackGestureHandler) - alternateBouncerView = null + override fun onViewDetachedFromWindow(view: View) {} + } + ) + } + } } private val onAttachAddBackGestureHandler = @@ -148,7 +166,7 @@ constructor( } private fun addViewToWindowManager() { - if (alternateBouncerView?.isAttachedToWindow == true) { + if (alternateBouncerView != null) { return } @@ -156,6 +174,7 @@ constructor( layoutInflater.get().inflate(R.layout.alternate_bouncer, null, false) as ConstraintLayout + Log.d(TAG, "Adding alternate bouncer view") windowManager.get().addView(alternateBouncerView, layoutParams) alternateBouncerView!!.addOnAttachStateChangeListener(onAttachAddBackGestureHandler) } @@ -304,6 +323,7 @@ constructor( } } } + companion object { private const val TAG = "AlternateBouncerViewBinder" private const val swipeTag = "AlternateBouncer-SWIPE" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 23c2491813f7..ba94f45528c7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewMode import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController +import com.android.systemui.util.kotlin.DisposableHandles import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -53,7 +54,15 @@ object KeyguardIndicationAreaBinder { viewModel: KeyguardIndicationAreaViewModel, indicationController: KeyguardIndicationController, ): DisposableHandle { - indicationController.setIndicationArea(view) + val disposables = DisposableHandles() + + // As the indication controller is a singleton, reset the view back to the previous view + // once the current view is disposed. + val previous = indicationController.indicationArea + indicationController.indicationArea = view + disposables += DisposableHandle { + previous?.let { indicationController.indicationArea = it } + } val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text) val indicationTextBottom: TextView = @@ -63,7 +72,7 @@ object KeyguardIndicationAreaBinder { view.clipToPadding = false val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) - val disposableHandle = + disposables += view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch("$TAG#viewModel.alpha") { @@ -124,9 +133,15 @@ object KeyguardIndicationAreaBinder { configurationBasedDimensions.value = loadFromResources(view) } } + + launch("$TAG#viewModel.visible") { + viewModel.visible.collect { visible -> + indicationController.setVisible(visible) + } + } } } - return disposableHandle + return disposables } private fun loadFromResources(view: View): ConfigurationBasedDimensions { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index d9a6d6401d64..62b47827fe8a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -55,6 +55,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, + private val lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, private val keyguardClockViewModel: KeyguardClockViewModel, @@ -74,13 +75,30 @@ constructor( burnInParams } return configurationInteractor - .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y) - .flatMapLatest { enterFromTopAmount -> + .dimensionPixelSize( + setOf( + R.dimen.keyguard_enter_from_top_translation_y, + R.dimen.keyguard_enter_from_side_translation_x, + ) + ) + .flatMapLatest { dimens -> combine( keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, burnIn(params).onStart { emit(BurnInModel()) }, goneToAodTransitionViewModel - .enterFromTopTranslationY(enterFromTopAmount) + .enterFromTopTranslationY( + dimens[R.dimen.keyguard_enter_from_top_translation_y]!! + ) + .onStart { emit(StateToValue()) }, + goneToAodTransitionViewModel + .enterFromSideTranslationX( + dimens[R.dimen.keyguard_enter_from_side_translation_x]!! + ) + .onStart { emit(StateToValue()) }, + lockscreenToAodTransitionViewModel + .enterFromSideTranslationX( + dimens[R.dimen.keyguard_enter_from_side_translation_x]!! + ) .onStart { emit(StateToValue()) }, occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart { emit(0f) @@ -88,21 +106,31 @@ constructor( aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart { emit(StateToValue()) }, - ) { - keyguardTranslationY, - burnInModel, - goneToAod, - occludedToLockscreen, - aodToLockscreen -> + ) { flows -> + val keyguardTranslationY = flows[0] as Float + val burnInModel = flows[1] as BurnInModel + val goneToAodTranslationY = flows[2] as StateToValue + val goneToAodTranslationX = flows[3] as StateToValue + val lockscreenToAodTranslationX = flows[4] as StateToValue + val occludedToLockscreen = flows[5] as Float + val aodToLockscreen = flows[6] as StateToValue + val translationY = if (aodToLockscreen.transitionState.isTransitioning()) { aodToLockscreen.value ?: 0f - } else if (goneToAod.transitionState.isTransitioning()) { - (goneToAod.value ?: 0f) + burnInModel.translationY + } else if (goneToAodTranslationY.transitionState.isTransitioning()) { + (goneToAodTranslationY.value ?: 0f) + burnInModel.translationY } else { burnInModel.translationY + occludedToLockscreen + keyguardTranslationY } - burnInModel.copy(translationY = translationY.toInt()) + val translationX = + burnInModel.translationX + + (goneToAodTranslationX.value ?: 0f) + + (lockscreenToAodTranslationX.value ?: 0f) + burnInModel.copy( + translationX = translationX.toInt(), + translationY = translationY.toInt(), + ) } } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index 46880883ac70..5ce1b5e3dcc5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -284,9 +284,9 @@ constructor( private fun DeviceEntryIconView.IconType.toAccessibilityHintType(): DeviceEntryIconView.AccessibilityHintType { return when (this) { + DeviceEntryIconView.IconType.FINGERPRINT, DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER - DeviceEntryIconView.IconType.FINGERPRINT, DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt index 74f7d75fa326..2bc8e51a64f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -26,13 +26,17 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason.FOLD import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.transform /** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */ @ExperimentalCoroutinesApi @@ -41,6 +45,7 @@ class GoneToAodTransitionViewModel @Inject constructor( deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + private val powerInteractor: PowerInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { @@ -56,13 +61,38 @@ constructor( /** y-translation from the top of the screen for AOD */ fun enterFromTopTranslationY(translatePx: Int): Flow<StateToValue> { - return transitionAnimation.sharedFlowWithState( - startTime = 600.milliseconds, - duration = 500.milliseconds, - onStep = { translatePx + it * -translatePx }, - onFinish = { 0f }, - interpolator = EMPHASIZED_DECELERATE, - ) + return transitionAnimation + .sharedFlowWithState( + startTime = 600.milliseconds, + duration = 500.milliseconds, + onStep = { translatePx + it * -translatePx }, + onFinish = { 0f }, + interpolator = EMPHASIZED_DECELERATE, + ) + .sample(powerInteractor.detailedWakefulness, ::Pair) + .transform { (stateToValue, wakefulness) -> + if (wakefulness.lastSleepReason != FOLD) { + emit(stateToValue) + } + } + } + + /** x-translation from the side of the screen for fold animation */ + fun enterFromSideTranslationX(translatePx: Int): Flow<StateToValue> { + return transitionAnimation + .sharedFlowWithState( + startTime = 500.milliseconds, + duration = 600.milliseconds, + onStep = { translatePx + it * -translatePx }, + onFinish = { 0f }, + interpolator = EMPHASIZED_DECELERATE, + ) + .sample(powerInteractor.detailedWakefulness, ::Pair) + .transform { (stateToValue, wakefulness) -> + if (wakefulness.lastSleepReason == FOLD) { + emit(stateToValue) + } + } } val notificationAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt index a758720d5e37..609b571d94fd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.doze.util.BurnInHelperWrapper @@ -28,9 +29,10 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.res.R +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import javax.inject.Inject -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -44,14 +46,15 @@ class KeyguardIndicationAreaViewModel @Inject constructor( private val keyguardInteractor: KeyguardInteractor, - private val bottomAreaInteractor: KeyguardBottomAreaInteractor, + bottomAreaInteractor: KeyguardBottomAreaInteractor, keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel, private val burnInHelperWrapper: BurnInHelperWrapper, - private val burnInInteractor: BurnInInteractor, - private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, + burnInInteractor: BurnInInteractor, + shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, configurationInteractor: ConfigurationInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, - @Background private val backgroundCoroutineContext: CoroutineContext, + communalSceneInteractor: CommunalSceneInteractor, + @Background private val backgroundDispatcher: CoroutineDispatcher, @Main private val mainDispatcher: CoroutineDispatcher, ) { @@ -61,6 +64,13 @@ constructor( /** An observable for the alpha level for the entire bottom area. */ val alpha: Flow<Float> = keyguardBottomAreaViewModel.alpha + /** An observable for the visibility value for the indication area view. */ + val visible: Flow<Boolean> = + anyOf( + keyguardInteractor.statusBarState.map { state -> state == StatusBarState.KEYGUARD }, + communalSceneInteractor.isCommunalVisible + ) + /** An observable for whether the indication area should be padded. */ val isIndicationAreaPadded: Flow<Boolean> = if (KeyguardBottomAreaRefactor.isEnabled) { @@ -97,7 +107,7 @@ constructor( ) } .distinctUntilChanged() - .flowOn(backgroundCoroutineContext) + .flowOn(backgroundDispatcher) /** An observable for the x-offset by which the indication area should be translated. */ val indicationAreaTranslationX: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index aefff7d0f79b..d7ac976ec00f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -251,6 +251,7 @@ constructor( goneToDreamingTransitionViewModel.lockscreenAlpha, goneToLockscreenTransitionViewModel.lockscreenAlpha, lockscreenToAodTransitionViewModel.lockscreenAlpha(viewState), + lockscreenToAodTransitionViewModel.lockscreenAlphaOnFold, lockscreenToDozingTransitionViewModel.lockscreenAlpha, lockscreenToDreamingTransitionViewModel.lockscreenAlpha, lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt index 8b5b347a763d..5408428c7072 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils +import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor @@ -24,12 +25,17 @@ import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason.FOLD +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.transform /** * Breaks down LOCKSCREEN->AOD transition into discrete steps for corresponding views to consume. @@ -40,6 +46,7 @@ class LockscreenToAodTransitionViewModel @Inject constructor( deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + private val powerInteractor: PowerInteractor, shadeDependentFlows: ShadeDependentFlows, animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { @@ -50,6 +57,12 @@ constructor( edge = Edge.create(from = LOCKSCREEN, to = AOD), ) + private val transitionAnimationOnFold = + animationFlow.setup( + duration = FromLockscreenTransitionInteractor.TO_AOD_FOLD_DURATION, + edge = Edge.create(from = LOCKSCREEN, to = AOD), + ) + val deviceEntryBackgroundViewAlpha: Flow<Float> = shadeDependentFlows.transitionFlow( flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), @@ -71,11 +84,64 @@ constructor( fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { var startAlpha = 1f - return transitionAnimation.sharedFlow( - duration = 500.milliseconds, - onStart = { startAlpha = viewState.alpha() }, - onStep = { MathUtils.lerp(startAlpha, 1f, it) }, - ) + return transitionAnimation + .sharedFlow( + duration = 500.milliseconds, + onStart = { startAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(startAlpha, 1f, it) }, + ) + .sample(powerInteractor.detailedWakefulness, ::Pair) + .transform { (alpha, wakefulness) -> + if (wakefulness.lastSleepReason != FOLD) { + emit(alpha) + } + } + } + + val lockscreenAlphaOnFold: Flow<Float> = + transitionAnimationOnFold + .sharedFlow( + startTime = 600.milliseconds, + duration = 500.milliseconds, + onStep = { it }, + ) + .sample(powerInteractor.detailedWakefulness, ::Pair) + .transform { (alpha, wakefulness) -> + if (wakefulness.lastSleepReason == FOLD) { + emit(alpha) + } + } + + val notificationAlphaOnFold: Flow<Float> = + transitionAnimationOnFold + .sharedFlow( + duration = 1100.milliseconds, + onStep = { 0f }, + onFinish = { 1f }, + ) + .sample(powerInteractor.detailedWakefulness, ::Pair) + .transform { (alpha, wakefulness) -> + if (wakefulness.lastSleepReason == FOLD) { + emit(alpha) + } + } + + /** x-translation from the side of the screen for fold animation */ + fun enterFromSideTranslationX(translatePx: Int): Flow<StateToValue> { + return transitionAnimationOnFold + .sharedFlowWithState( + startTime = 600.milliseconds, + duration = 500.milliseconds, + onStep = { translatePx + it * -translatePx }, + onFinish = { 0f }, + interpolator = EMPHASIZED_DECELERATE, + ) + .sample(powerInteractor.detailedWakefulness, ::Pair) + .transform { (stateToValue, wakefulness) -> + if (wakefulness.lastSleepReason == FOLD) { + emit(stateToValue) + } + } } override val deviceEntryParentViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt index e4465acb14ab..6351d7d28d07 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt @@ -19,10 +19,13 @@ package com.android.systemui.log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize +import com.android.systemui.log.echo.LogcatEchoTrackerAlways import javax.inject.Inject @SysUISingleton -class LogBufferFactory @Inject constructor( +class LogBufferFactory +@Inject +constructor( private val dumpManager: DumpManager, private val logcatEchoTracker: LogcatEchoTracker ) { @@ -30,9 +33,11 @@ class LogBufferFactory @Inject constructor( fun create( name: String, maxSize: Int, - systrace: Boolean = true + systrace: Boolean = true, + alwaysLogToLogcat: Boolean = false, ): LogBuffer { - val buffer = LogBuffer(name, adjustMaxSize(maxSize), logcatEchoTracker, systrace) + val echoTracker = if (alwaysLogToLogcat) LogcatEchoTrackerAlways else logcatEchoTracker + val buffer = LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace) dumpManager.registerBuffer(name, buffer) return buffer } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 1e79f429cee6..c7fde48dc0ea 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -622,7 +622,8 @@ public class LogModule { @SysUISingleton @SceneFrameworkLog public static LogBuffer provideSceneFrameworkLogBuffer(LogBufferFactory factory) { - return factory.create("SceneFramework", 50); + return factory + .create("SceneFramework", 50, /* systrace */ true, /* alwaysLogToLogcat */ true); } /** Provides a {@link LogBuffer} for the bluetooth QS tile dialog. */ diff --git a/packages/SystemUI/src/com/android/systemui/log/echo/LogcatEchoTrackerAlways.kt b/packages/SystemUI/src/com/android/systemui/log/echo/LogcatEchoTrackerAlways.kt new file mode 100644 index 000000000000..ce096b306651 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/echo/LogcatEchoTrackerAlways.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.log.echo + +import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel + +/** + * The buffer and all of its tags will be logged to logcat at all times. + * + * This can be used for buffers that are important and should appear in bugreports in logcat + * directly. + */ +object LogcatEchoTrackerAlways : LogcatEchoTracker { + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = true + + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true +} diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index 1b3b47350197..988fe640de2d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -43,7 +43,7 @@ import java.util.LinkedList; public class NotificationPlayer implements OnCompletionListener, OnErrorListener { private static final int PLAY = 1; private static final int STOP = 2; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; private static final class Command { int code; diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 3ab0420e12b7..e7c2a454e16c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -53,7 +53,7 @@ import javax.inject.Inject; @SysUISingleton public class RingtonePlayer implements CoreStartable { private static final String TAG = "RingtonePlayer"; - private static final boolean LOGD = false; + private static final boolean LOGD = true; private final Context mContext; // TODO: support Uri switching under same IBinder diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt index 6a91d1b15325..a2d7fb152e5c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt @@ -26,6 +26,8 @@ import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel +import com.android.systemui.media.controls.util.MediaSmartspaceLogger +import com.android.systemui.media.controls.util.SmallHash import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.time.SystemClock import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow @@ -43,9 +45,10 @@ import kotlinx.coroutines.flow.asStateFlow class MediaFilterRepository @Inject constructor( - @Application applicationContext: Context, + @Application private val applicationContext: Context, private val systemClock: SystemClock, private val configurationController: ConfigurationController, + private val smartspaceLogger: MediaSmartspaceLogger, ) { val onAnyMediaConfigurationChange: Flow<Unit> = conflatedCallbackFlow { @@ -211,6 +214,12 @@ constructor( isMediaFromRec(it) ) sortedMap[sortKey] = newCommonModel + val isUpdate = + sortedMedia.values.any { commonModel -> + commonModel is MediaCommonModel.MediaControl && + commonModel.mediaLoadedModel.instanceId == + mediaDataLoadingModel.instanceId + } // On Addition or tapping on recommendations, we should show the new order of media. if (mediaFromRecPackageName == it.packageName) { @@ -218,30 +227,50 @@ constructor( mediaFromRecPackageName = null _currentMedia.value = sortedMap.values.toList() } - } else if (sortedMap.size > _currentMedia.value.size && it.active) { - _currentMedia.value = sortedMap.values.toList() } else { - // When loading an update for an existing media control. + var isNewToCurrentMedia = true val currentList = mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) } currentList.forEachIndexed { index, mediaCommonModel -> if ( mediaCommonModel is MediaCommonModel.MediaControl && mediaCommonModel.mediaLoadedModel.instanceId == - mediaDataLoadingModel.instanceId && - mediaCommonModel != newCommonModel + mediaDataLoadingModel.instanceId ) { - // Update media model if changed. - currentList[index] = newCommonModel + // When loading an update for an existing media control. + isNewToCurrentMedia = false + if (mediaCommonModel != newCommonModel) { + // Update media model if changed. + currentList[index] = newCommonModel + } } } - _currentMedia.value = currentList + if (isNewToCurrentMedia && it.active) { + _currentMedia.value = sortedMap.values.toList() + } else { + _currentMedia.value = currentList + } + } + + sortedMedia = sortedMap + + if (!isUpdate) { + val rank = sortedMedia.values.indexOf(newCommonModel) + if (isSmartspaceLoggingEnabled(newCommonModel, rank)) { + smartspaceLogger.logSmartspaceCardReceived( + it.smartspaceId, + it.appUid, + cardinality = _currentMedia.value.size, + isSsReactivated = mediaDataLoadingModel.isSsReactivated, + rank = rank, + ) + } + } else if (mediaDataLoadingModel.receivedSmartspaceCardLatency != 0) { + logSmartspaceAllMediaCards(mediaDataLoadingModel.receivedSmartspaceCardLatency) } } } - sortedMedia = sortedMap - // On removal we want to keep the order being shown to user. if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) { _currentMedia.value = @@ -249,6 +278,7 @@ constructor( commonModel !is MediaCommonModel.MediaControl || mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId } + sortedMedia = sortedMap } } @@ -271,21 +301,45 @@ constructor( isPlaying = false, active = _smartspaceMediaData.value.isActive, ) + val newCommonModel = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) when (smartspaceMediaLoadingModel) { - is SmartspaceMediaLoadingModel.Loaded -> - sortedMap[sortKey] = - MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) - is SmartspaceMediaLoadingModel.Removed -> + is SmartspaceMediaLoadingModel.Loaded -> { + sortedMap[sortKey] = newCommonModel + _currentMedia.value = sortedMap.values.toList() + sortedMedia = sortedMap + + if (isRecommendationActive()) { + val hasActivatedExistedResumeMedia = + !hasActiveMedia() && + hasAnyMedia() && + smartspaceMediaLoadingModel.isPrioritized + if (hasActivatedExistedResumeMedia) { + // Log resume card received if resumable media card is reactivated and + // recommendation card is valid and ranked first + logSmartspaceAllMediaCards( + (systemClock.currentTimeMillis() - + _smartspaceMediaData.value.headphoneConnectionTimeMillis) + .toInt() + ) + } + + smartspaceLogger.logSmartspaceCardReceived( + SmallHash.hash(_smartspaceMediaData.value.targetId), + _smartspaceMediaData.value.getUid(applicationContext), + cardinality = _currentMedia.value.size, + isRecommendationCard = true, + rank = _currentMedia.value.indexOf(newCommonModel), + ) + } + } + is SmartspaceMediaLoadingModel.Removed -> { _currentMedia.value = _currentMedia.value.filter { commonModel -> commonModel !is MediaCommonModel.MediaRecommendations } + sortedMedia = sortedMap + } } - - if (sortedMap.size > sortedMedia.size) { - _currentMedia.value = sortedMap.values.toList() - } - sortedMedia = sortedMap } fun setOrderedMedia() { @@ -315,4 +369,35 @@ constructor( private fun isMediaFromRec(data: MediaData): Boolean { return data.isPlaying == true && mediaFromRecPackageName == data.packageName } + + /** Log all media cards if smartspace logging is enabled for each. */ + private fun logSmartspaceAllMediaCards(receivedSmartspaceCardLatency: Int) { + sortedMedia.values.forEachIndexed { index, mediaCommonModel -> + if (mediaCommonModel is MediaCommonModel.MediaControl) { + _selectedUserEntries.value[mediaCommonModel.mediaLoadedModel.instanceId]?.let { + it.smartspaceId = + SmallHash.hash(it.appUid + systemClock.currentTimeMillis().toInt()) + it.isImpressed = false + + if (isSmartspaceLoggingEnabled(mediaCommonModel, index)) { + smartspaceLogger.logSmartspaceCardReceived( + it.smartspaceId, + it.appUid, + cardinality = _currentMedia.value.size, + isSsReactivated = mediaCommonModel.mediaLoadedModel.isSsReactivated, + rank = index, + receivedLatencyMillis = receivedSmartspaceCardLatency, + ) + } + } + } + } + } + + private fun isSmartspaceLoggingEnabled(commonModel: MediaCommonModel, index: Int): Boolean { + return sortedMedia.size > index && + (_smartspaceMediaData.value.expiryTimeMs != 0L || + isRecommendationActive() || + commonModel is MediaCommonModel.MediaRecommendations) + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt index f78a0f959f3c..31bd4fb78ffa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt @@ -180,7 +180,13 @@ constructor( mediaData.instanceId ) mediaFilterRepository.addMediaDataLoadingState( - MediaDataLoadingModel.Loaded(lastActiveId) + MediaDataLoadingModel.Loaded( + lastActiveId, + receivedSmartspaceCardLatency = + (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) + .toInt(), + isSsReactivated = true + ) ) mediaLoadingLogger.logMediaLoaded( mediaData.instanceId, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index 37dffd1955d6..adcfba75f498 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -86,6 +86,7 @@ import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger +import com.android.systemui.media.controls.util.SmallHash import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.res.R @@ -721,6 +722,7 @@ class MediaDataProcessor( appUid = appUid, isExplicit = isExplicit, resumeProgress = progress, + smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()), ) ) } @@ -902,6 +904,7 @@ class MediaDataProcessor( instanceId = instanceId, appUid = appUid, isExplicit = isExplicit, + smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()), ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt index 8b2f619374d1..eab0d483c3e5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt @@ -354,10 +354,21 @@ constructor( activeDevice = routingSession?.let { + val icon = if (it.selectedRoutes.size > 1) { + context.getDrawable( + com.android.settingslib.R.drawable.ic_media_group_device) + } else { + connectedDevice?.icon // Single route. We don't change the icon. + } // For a remote session, always use the current device from - // LocalMediaManager. Override with routing session name if available to - // show dynamic group name. - connectedDevice?.copy(name = it.name ?: connectedDevice.name) + // LocalMediaManager. Override with routing session information if + // available: + // - Name: To show the dynamic group name. + // - Icon: To show the group icon if there's more than one selected + // route. + connectedDevice?.copy( + name = it.name ?: connectedDevice.name, + icon = icon) } ?: MediaDeviceData( enabled = false, icon = context.getDrawable(R.drawable.ic_media_home_devices), diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt index 11a562911a85..40b34779151d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt @@ -99,6 +99,12 @@ data class MediaData( /** Track progress (0 - 1) to display for players where [resumption] is true */ val resumeProgress: Double? = null, + + /** Smartspace Id, used for logging. */ + var smartspaceId: Int = -1, + + /** If media card was visible to user, used for logging. */ + var isImpressed: Boolean = false, ) { companion object { /** Media is playing on the local device */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt index 170f1f78a5a6..c8a02faea58a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt @@ -27,6 +27,8 @@ sealed class MediaDataLoadingModel { data class Loaded( override val instanceId: InstanceId, val immediatelyUpdateUi: Boolean = true, + val receivedSmartspaceCardLatency: Int = 0, + val isSsReactivated: Boolean = false, ) : MediaDataLoadingModel() /** Media data has been removed. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt index 9e15dbbb64b8..96c3fa8fbc89 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt @@ -48,6 +48,8 @@ data class SmartspaceMediaData( val instanceId: InstanceId? = null, /** The timestamp in milliseconds indicating when the card should be removed */ val expiryTimeMs: Long = 0L, + /** If recommendation card was visible to user, used for logging. */ + var isImpressed: Boolean = false, ) { /** * Indicates if all the data is valid. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt new file mode 100644 index 000000000000..01fbf4af7626 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.media.controls.util + +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shared.system.SysUiStatsLog +import javax.inject.Inject + +/** Logger class for Smartspace logging events. */ +@SysUISingleton +class MediaSmartspaceLogger @Inject constructor() { + /** + * Log Smartspace card received event + * + * @param instanceId id to uniquely identify a card. + * @param uid uid for the application that media comes from. + * @param cardinality number of card in carousel. + * @param isRecommendationCard whether media card being logged is a recommendations card. + * @param isSsReactivated indicates resume media card is reactivated by Smartspace + * recommendation signal + * @param rank the rank for media card in the media carousel, starting from 0 + * @param receivedLatencyMillis latency in milliseconds for card received events. + */ + fun logSmartspaceCardReceived( + instanceId: Int, + uid: Int, + cardinality: Int, + isRecommendationCard: Boolean = false, + isSsReactivated: Boolean = false, + rank: Int = 0, + receivedLatencyMillis: Int = 0, + ) { + logSmartspaceCardReported( + SMARTSPACE_CARD_RECEIVED_EVENT, + instanceId, + uid, + surfaces = + intArrayOf( + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY, + ), + cardinality, + isRecommendationCard, + isSsReactivated, + rank = rank, + receivedLatencyMillis = receivedLatencyMillis, + ) + } + + /** + * Log Smartspace card UI event + * + * @param eventId id of the event. eg: dismiss, click, or seen. + * @param instanceId id to uniquely identify a card. + * @param uid uid for the application that media comes from. + * @param location location of media carousel holding media card. + * @param cardinality number of card in carousel. + * @param isRecommendationCard whether media card being logged is a recommendations card. + * @param isSsReactivated indicates resume media card is reactivated by Smartspace + * recommendation signal + * @param rank the rank for media card in the media carousel, starting from 0 + * @param isSwipeToDismiss whether is to log swipe-to-dismiss event + */ + fun logSmartspaceCardUIEvent( + eventId: Int, + instanceId: Int, + uid: Int, + location: Int, + cardinality: Int, + isRecommendationCard: Boolean = false, + isSsReactivated: Boolean = false, + rank: Int = 0, + isSwipeToDismiss: Boolean = false, + ) { + logSmartspaceCardReported( + eventId, + instanceId, + uid, + surfaces = intArrayOf(location), + cardinality, + isRecommendationCard, + isSsReactivated, + rank = rank, + isSwipeToDismiss = isSwipeToDismiss, + ) + } + + /** + * Log Smartspace events + * + * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN) + * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new + * instanceId + * @param uid uid for the application that media comes from + * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when + * the event happened + * @param cardinality number of card in carousel. + * @param isRecommendationCard whether media card being logged is a recommendations card. + * @param isSsReactivated indicates resume media card is reactivated by Smartspace + * recommendation signal + * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1 + * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc. + * @param interactedSubcardCardinality how many media items were shown to the user when there is + * user interaction + * @param rank the rank for media card in the media carousel, starting from 0 + * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency + * between headphone connection to sysUI displays media recommendation card + * @param isSwipeToDismiss whether is to log swipe-to-dismiss event + */ + private fun logSmartspaceCardReported( + eventId: Int, + instanceId: Int, + uid: Int, + surfaces: IntArray, + cardinality: Int, + isRecommendationCard: Boolean, + isSsReactivated: Boolean, + interactedSubcardRank: Int = 0, + interactedSubcardCardinality: Int = 0, + rank: Int = 0, + receivedLatencyMillis: Int = 0, + isSwipeToDismiss: Boolean = false, + ) { + surfaces.forEach { surface -> + SysUiStatsLog.write( + SysUiStatsLog.SMARTSPACE_CARD_REPORTED, + eventId, + instanceId, + // Deprecated, replaced with AiAi feature type so we don't need to create logging + // card type for each new feature. + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, + surface, + // Use -1 as rank value to indicate user swipe to dismiss the card + if (isSwipeToDismiss) -1 else rank, + cardinality, + if (isRecommendationCard) { + 15 // MEDIA_RECOMMENDATION + } else if (isSsReactivated) { + 43 // MEDIA_RESUME_SS_ACTIVATED + } else { + 31 // MEDIA_RESUME + }, + uid, + interactedSubcardRank, + interactedSubcardCardinality, + receivedLatencyMillis, + null, // Media cards cannot have subcards. + null // Media cards don't have dimensions today. + ) + + if (DEBUG) { + Log.d( + TAG, + "Log Smartspace card event id: $eventId instance id: $instanceId" + + " surface: $surface rank: $rank cardinality: $cardinality " + + "isRecommendationCard: $isRecommendationCard " + + "isSsReactivated: $isSsReactivated" + + "uid: $uid " + + "interactedSubcardRank: $interactedSubcardRank " + + "interactedSubcardCardinality: $interactedSubcardCardinality " + + "received_latency_millis: $receivedLatencyMillis" + ) + } + } + } + + companion object { + private const val TAG = "MediaSmartspaceLogger" + private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + private const val SMARTSPACE_CARD_RECEIVED_EVENT = 759 + const val SMARTSPACE_CARD_CLICK_EVENT = 760 + const val SMARTSPACE_CARD_DISMISS_EVENT = 761 + const val SMARTSPACE_CARD_SEEN_EVENT = 800 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 1e8656311f1d..b48b40986f73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -85,6 +85,7 @@ import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.media.dialog.MediaItem.MediaItemType; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; @@ -110,6 +111,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -661,28 +663,38 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, /* connectedMediaDevice */ null, devices, needToHandleMutingExpectedDevice); + } else { + // selected device exist + return categorizeMediaItemsLocked( + connectedMediaDevice, + devices, + /* needToHandleMutingExpectedDevice */ false); } - // selected device exist - return categorizeMediaItemsLocked( - connectedMediaDevice, - devices, - /* needToHandleMutingExpectedDevice */ false); } // To keep the same list order final List<MediaDevice> targetMediaDevices = new ArrayList<>(); final Map<Integer, MediaItem> dividerItems = new HashMap<>(); + + Map<String, MediaDevice> idToMediaDeviceMap = + devices.stream() + .collect(Collectors.toMap(MediaDevice::getId, Function.identity())); + for (MediaItem originalMediaItem : oldMediaItems) { - for (MediaDevice newDevice : devices) { - if (originalMediaItem.getMediaDevice().isPresent() - && TextUtils.equals(originalMediaItem.getMediaDevice().get().getId(), - newDevice.getId())) { - targetMediaDevices.add(newDevice); - break; + switch (originalMediaItem.getMediaItemType()) { + case MediaItemType.TYPE_GROUP_DIVIDER -> { + dividerItems.put( + oldMediaItems.indexOf(originalMediaItem), originalMediaItem); + } + case MediaItemType.TYPE_DEVICE -> { + String originalMediaItemId = + originalMediaItem.getMediaDevice().orElseThrow().getId(); + if (idToMediaDeviceMap.containsKey(originalMediaItemId)) { + targetMediaDevices.add(idToMediaDeviceMap.get(originalMediaItemId)); + } + } + case MediaItemType.TYPE_PAIR_NEW_DEVICE -> { + // Do nothing. } - } - if (originalMediaItem.getMediaItemType() - == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) { - dividerItems.put(oldMediaItems.indexOf(originalMediaItem), originalMediaItem); } } if (targetMediaDevices.size() != devices.size()) { @@ -723,12 +735,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device)); } else { if (device.isSuggestedDevice() && !suggestedDeviceAdded) { - attachGroupDivider(finalMediaItems, mContext.getString( - R.string.media_output_group_title_suggested_device)); + addSuggestedDeviceGroupDivider(finalMediaItems); suggestedDeviceAdded = true; } else if (!device.isSuggestedDevice() && !displayGroupAdded) { - attachGroupDivider(finalMediaItems, mContext.getString( - R.string.media_output_group_title_speakers_and_displays)); + addSpeakersAndDisplaysGroupDivider(finalMediaItems); displayGroupAdded = true; } finalMediaItems.add(MediaItem.createDeviceMediaItem(device)); @@ -738,8 +748,17 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return finalMediaItems; } - private void attachGroupDivider(List<MediaItem> mediaItems, String title) { - mediaItems.add(MediaItem.createGroupDividerMediaItem(title)); + private void addSuggestedDeviceGroupDivider(List<MediaItem> mediaItems) { + mediaItems.add( + MediaItem.createGroupDividerMediaItem( + mContext.getString(R.string.media_output_group_title_suggested_device))); + } + + private void addSpeakersAndDisplaysGroupDivider(List<MediaItem> mediaItems) { + mediaItems.add( + MediaItem.createGroupDividerMediaItem( + mContext.getString( + R.string.media_output_group_title_speakers_and_displays))); } private void attachConnectNewDeviceItemIfNeeded(List<MediaItem> mediaItems) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt index ee8169423de2..47e069102035 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt @@ -98,7 +98,7 @@ constructor( createAndShow( packageName = null, aboveStatusBar = false, - dialogTransitionAnimatorController = null, + dialogTransitionAnimatorController = controller, includePlaybackAndAppMetadata = false, userHandle = null, ) diff --git a/packages/SystemUI/src/com/android/systemui/navigation/data/repository/NavigationRepository.kt b/packages/SystemUI/src/com/android/systemui/navigation/data/repository/NavigationRepository.kt new file mode 100644 index 000000000000..4409e2c6ae59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigation/data/repository/NavigationRepository.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.navigation.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.shared.system.QuickStepContract +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class NavigationRepository +@Inject +constructor( + private val controller: NavigationModeController, +) { + + /** Whether the current navigation bar mode is edge-to-edge. */ + val isGesturalMode: Flow<Boolean> = conflatedCallbackFlow { + val listener = + NavigationModeController.ModeChangedListener { mode -> + trySend(QuickStepContract.isGesturalMode(mode)) + } + + val currentMode = controller.addListener(listener) + trySend(QuickStepContract.isGesturalMode(currentMode)) + + awaitClose { controller.removeListener(listener) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigation/domain/interactor/NavigationInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigation/domain/interactor/NavigationInteractor.kt new file mode 100644 index 000000000000..0f9c883b70c7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigation/domain/interactor/NavigationInteractor.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.navigation.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.navigation.data.repository.NavigationRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class NavigationInteractor +@Inject +constructor( + repository: NavigationRepository, +) { + + /** Whether the current navigation bar mode is edge-to-edge. */ + val isGesturalMode: Flow<Boolean> = repository.isGesturalMode +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 80c43796a55b..2e5ff9ddb477 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -56,6 +56,8 @@ import android.view.WindowInsets; import android.view.accessibility.AccessibilityManager; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.android.internal.accessibility.common.ShortcutConstants; import com.android.systemui.Dumpable; @@ -64,12 +66,14 @@ import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; 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.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; +import com.android.systemui.shared.rotation.RotationPolicyUtil; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -108,6 +112,7 @@ public final class NavBarHelper implements private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Executor mMainExecutor; + private final Handler mBgHandler; private final AccessibilityManager mAccessibilityManager; private final Lazy<AssistManager> mAssistManagerLazy; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; @@ -160,13 +165,15 @@ public final class NavBarHelper implements // Listens for changes to display rotation private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() { + @WorkerThread @Override public void onRotationChanged(final int rotation) { // We need this to be scheduled as early as possible to beat the redrawing of // window in response to the orientation change. + @Nullable Boolean isRotationLocked = RotationPolicyUtil.isRotationLocked(mContext); mHandler.postAtFrontOfQueue(() -> { mRotationWatcherRotation = rotation; - dispatchRotationChanged(rotation); + dispatchRotationChanged(rotation, isRotationLocked); }); } }; @@ -194,7 +201,8 @@ public final class NavBarHelper implements ConfigurationController configurationController, DumpManager dumpManager, CommandQueue commandQueue, - @Main Executor mainExecutor) { + @Main Executor mainExecutor, + @Background Handler bgHandler) { // b/319489709: This component shouldn't be running for a non-primary user if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) { Log.wtf(TAG, "Unexpected initialization for non-primary user", new Throwable()); @@ -215,6 +223,7 @@ public final class NavBarHelper implements mDefaultDisplayId = displayTracker.getDefaultDisplayId(); mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context); mMainExecutor = mainExecutor; + mBgHandler = bgHandler; mNavBarMode = navigationModeController.addListener(this); mCommandQueue.addCallback(this); @@ -322,7 +331,13 @@ public final class NavBarHelper implements listener.updateAssistantAvailable(mAssistantAvailable, mLongPressHomeEnabled); } listener.updateWallpaperVisibility(mWallpaperVisible, mDefaultDisplayId); - listener.updateRotationWatcherState(mRotationWatcherRotation); + + mBgHandler.post(() -> { + Boolean isRotationLocked = RotationPolicyUtil.isRotationLocked(mContext); + mMainExecutor.execute( + () -> listener.updateRotationWatcherState( + mRotationWatcherRotation, isRotationLocked)); + }); } /** @@ -526,9 +541,9 @@ public final class NavBarHelper implements } } - private void dispatchRotationChanged(int rotation) { + private void dispatchRotationChanged(int rotation, @Nullable Boolean isRotationLocked) { for (NavbarTaskbarStateUpdater listener : mStateListeners) { - listener.updateRotationWatcherState(rotation); + listener.updateRotationWatcherState(rotation, isRotationLocked); } } @@ -544,7 +559,7 @@ public final class NavBarHelper implements void updateAccessibilityServicesState(); void updateAssistantAvailable(boolean available, boolean longPressHomeEnabled); default void updateWallpaperVisibility(boolean visible, int displayId) {} - default void updateRotationWatcherState(int rotation) {} + default void updateRotationWatcherState(int rotation, @Nullable Boolean isRotationLocked) {} } /** Data class to help Taskbar/Navbar initiate state correctly when switching between the two.*/ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 07289cb7b809..69aa450a9d4c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -136,6 +136,7 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.rotation.RotationButtonController; +import com.android.systemui.shared.rotation.RotationPolicyUtil; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -366,9 +367,11 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } @Override - public void updateRotationWatcherState(int rotation) { + public void updateRotationWatcherState( + int rotation, @Nullable Boolean isRotationLocked) { if (mIsOnDefaultDisplay && mView != null) { - mView.getRotationButtonController().onRotationWatcherChanged(rotation); + RotationButtonController controller = mView.getRotationButtonController(); + controller.onRotationWatcherChanged(rotation, isRotationLocked); if (mView.needsReorient(rotation)) { repositionNavigationBar(rotation); } @@ -819,8 +822,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements // Reset user rotation pref to match that of the WindowManager if starting in locked // mode. This will automatically happen when switching from auto-rotate to locked mode. - if (display != null && rotationButtonController.isRotationLocked()) { - rotationButtonController.setRotationLockedAtAngle( + @Nullable Boolean isRotationLocked = RotationPolicyUtil.isRotationLocked(mContext); + if (display != null && isRotationLocked) { + rotationButtonController.setRotationLockedAtAngle(isRotationLocked, display.getRotation(), /* caller= */ "NavigationBar#onViewAttached"); } } else { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index b177b0b4e4b3..1c2a0871f3f9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -23,6 +23,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGAT import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification; +import static com.android.wm.shell.Flags.enableTaskbarOnPhones; import android.content.Context; import android.content.pm.ActivityInfo; @@ -285,8 +286,10 @@ public class NavigationBarControllerImpl implements @VisibleForTesting boolean supportsTaskbar() { - // Enable for tablets, unfolded state on a foldable device or (non handheld AND flag is set) - return mIsLargeScreen || (!mIsPhone && enableTaskbarNavbarUnification()); + // Enable for tablets, unfolded state on a foldable device, (non handheld AND flag is set), + // or handheld when enableTaskbarOnPhones() returns true. + boolean foldedOrPhone = !mIsPhone || enableTaskbarOnPhones(); + return mIsLargeScreen || (foldedOrPhone && enableTaskbarNavbarUnification()); } private final CommandQueue.Callbacks mCommandQueueCallbacks = new CommandQueue.Callbacks() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 24b7a011f093..96df7286d460 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -224,6 +224,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements }); } + boolean isBound() { + return mBound.get(); + } + @WorkerThread private void setBindService(boolean bind) { if (mBound.get() && mUnbindImmediate.get()) { 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 6bc5095ed1ea..d10471d86d0b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -217,7 +217,11 @@ public class TileServiceManager { Log.e(TAG, "Service already bound"); return; } - mPendingBind = true; + if (!mStateManager.isBound()) { + // If we are bound, we don't need to set a pending bind. There's either one already or + // we are fully bound. + mPendingBind = true; + } mBound = true; mJustBound = true; mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME); diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PaginatedBaseLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PaginatedBaseLayoutType.kt new file mode 100644 index 000000000000..0285cbdbfac1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PaginatedBaseLayoutType.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.dagger + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class PaginatedBaseLayoutType diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index 7b679932de3e..c2143619c1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -30,18 +30,21 @@ import com.android.systemui.qs.panels.shared.model.GridConsistencyLog import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType import com.android.systemui.qs.panels.ui.compose.GridLayout import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout +import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout +import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel +import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModelImpl import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModelImpl -import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel -import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModelImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -62,14 +65,24 @@ interface PanelsModule { @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel - @Binds fun bindGridSizeViewModel(impl: InfiniteGridSizeViewModelImpl): InfiniteGridSizeViewModel + @Binds fun bindGridSizeViewModel(impl: FixedColumnsSizeViewModelImpl): FixedColumnsSizeViewModel @Binds fun bindIconLabelVisibilityViewModel( impl: IconLabelVisibilityViewModelImpl ): IconLabelVisibilityViewModel - @Binds @Named("Default") fun bindDefaultGridLayout(impl: PartitionedGridLayout): GridLayout + @Binds + @PaginatedBaseLayoutType + fun bindPaginatedBaseGridLayout(impl: PartitionedGridLayout): PaginatableGridLayout + + @Binds + @PaginatedBaseLayoutType + fun bindPaginatedBaseConsistencyInteractor( + impl: NoopGridConsistencyInteractor + ): GridTypeConsistencyInteractor + + @Binds @Named("Default") fun bindDefaultGridLayout(impl: PaginatedGridLayout): GridLayout companion object { @Provides @@ -109,6 +122,14 @@ interface PanelsModule { } @Provides + @IntoSet + fun providePaginatedGridLayout( + gridLayout: PaginatedGridLayout + ): Pair<GridLayoutType, GridLayout> { + return Pair(PaginatedGridLayoutType, gridLayout) + } + + @Provides fun provideGridLayoutMap( entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>> ): Map<GridLayoutType, GridLayout> { @@ -147,6 +168,14 @@ interface PanelsModule { } @Provides + @IntoSet + fun providePaginatedGridConsistencyInteractor( + @PaginatedBaseLayoutType consistencyInteractor: GridTypeConsistencyInteractor, + ): Pair<GridLayoutType, GridTypeConsistencyInteractor> { + return Pair(PaginatedGridLayoutType, consistencyInteractor) + } + + @Provides fun provideGridConsistencyInteractorMap( entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>> ): Map<GridLayoutType, GridTypeConsistencyInteractor> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepository.kt index 43ccdf663478..32ce973cbe58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepository.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @SysUISingleton -class InfiniteGridSizeRepository @Inject constructor() { +class FixedColumnsRepository @Inject constructor() { // Number of columns in the narrowest state for consistency private val _columns = MutableStateFlow(4) val columns: StateFlow<Int> = _columns.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt index 44d8688f0c4d..47c4ffd6a2cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.shared.model.GridLayoutType -import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType +import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,13 +26,14 @@ import kotlinx.coroutines.flow.asStateFlow interface GridLayoutTypeRepository { val layout: StateFlow<GridLayoutType> + fun setLayout(type: GridLayoutType) } @SysUISingleton class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository { private val _layout: MutableStateFlow<GridLayoutType> = - MutableStateFlow(PartitionedGridLayoutType) + MutableStateFlow(PaginatedGridLayoutType) override val layout = _layout.asStateFlow() override fun setLayout(type: GridLayoutType) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt new file mode 100644 index 000000000000..26b2e2b7bd66 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import android.content.res.Resources +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.util.kotlin.emitOnStart +import javax.inject.Inject +import kotlinx.coroutines.flow.map + +/** + * Provides the number of [rows] to use with a paginated grid, by tracking the resource + * [R.integer.quick_settings_max_rows]. + */ +@SysUISingleton +class PaginatedGridRepository +@Inject +constructor( + @Main private val resources: Resources, + configurationRepository: ConfigurationRepository, +) { + val rows = + configurationRepository.onConfigurationChange.emitOnStart().map { + resources.getInteger(R.integer.quick_settings_max_rows) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractor.kt index 13c6072340c6..9591002b3d3d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractor.kt @@ -17,11 +17,11 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.panels.data.repository.InfiniteGridSizeRepository +import com.android.systemui.qs.panels.data.repository.FixedColumnsRepository import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow @SysUISingleton -class InfiniteGridSizeInteractor @Inject constructor(repo: InfiniteGridSizeRepository) { +class FixedColumnsSizeInteractor @Inject constructor(repo: FixedColumnsRepository) { val columns: StateFlow<Int> = repo.columns } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt index e99c64c8c1f3..0fe79af06a54 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt @@ -28,7 +28,7 @@ class InfiniteGridConsistencyInteractor @Inject constructor( private val iconTilesInteractor: IconTilesInteractor, - private val gridSizeInteractor: InfiniteGridSizeInteractor + private val gridSizeInteractor: FixedColumnsSizeInteractor ) : GridTypeConsistencyInteractor { /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractor.kt index 97ceacc6926d..d7d1ce9797ee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractor.kt @@ -17,10 +17,14 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.panels.data.repository.PaginatedGridRepository import javax.inject.Inject @SysUISingleton -class NoopConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor { - override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles +class PaginatedGridInteractor +@Inject +constructor(paginatedGridRepository: PaginatedGridRepository) { + val rows = paginatedGridRepository.rows + + val defaultRows = 4 } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt index 9550ddb30e78..b1942fee9b6d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt @@ -34,3 +34,6 @@ data object StretchedGridLayoutType : GridLayoutType /** Grid type grouping large tiles on top and icon tiles at the bottom. */ data object PartitionedGridLayoutType : GridLayoutType + +/** Grid type for a paginated list of tiles. It will delegate to some other layout type. */ +data object PaginatedGridLayoutType : GridLayoutType diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt index 8806931a888a..e2f6bcf2e872 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -18,15 +18,19 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec +/** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */ interface GridLayout { @Composable fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, + editModeStart: () -> Unit, ) @Composable @@ -37,3 +41,49 @@ interface GridLayout { onRemoveTile: (TileSpec) -> Unit, ) } + +/** + * A type of [GridLayout] that can be paginated, to use together with [PaginatedGridLayout]. + * + * [splitIntoPages] determines how to split a list of tiles based on the number of rows and columns + * available. + */ +interface PaginatableGridLayout : GridLayout { + fun splitIntoPages( + tiles: List<TileViewModel>, + rows: Int, + columns: Int, + ): List<List<TileViewModel>> + + companion object { + + /** + * Splits a list of [SizedTile] into rows, each with at most [columns] occupied. + * + * It will leave gaps at the end of a row if the next [SizedTile] has [SizedTile.width] that + * is larger than the space remaining in the row. + */ + fun splitInRows( + tiles: List<SizedTile<TileViewModel>>, + columns: Int + ): List<List<SizedTile<TileViewModel>>> { + val row = TileRow<TileViewModel>(columns) + + return buildList { + for (tile in tiles) { + check(tile.width <= columns) + if (!row.maybeAddTile(tile)) { + // Couldn't add tile to previous row, create a row with the current tiles + // and start a new one + add(row.tiles) + row.clear() + row.maybeAddTile(tile) + } + } + if (row.tiles.isNotEmpty()) { + add(row.tiles) + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt index 2f0fe221a2b4..ea97f0de2bb8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt @@ -26,9 +26,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel -import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.res.R @@ -39,13 +40,14 @@ class InfiniteGridLayout @Inject constructor( private val iconTilesViewModel: IconTilesViewModel, - private val gridSizeViewModel: InfiniteGridSizeViewModel, -) : GridLayout { + private val gridSizeViewModel: FixedColumnsSizeViewModel, +) : PaginatableGridLayout { @Composable override fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, + editModeStart: () -> Unit, ) { DisposableEffect(tiles) { val token = Any() @@ -55,16 +57,8 @@ constructor( val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { - items( - tiles.size, - span = { index -> - if (iconTilesViewModel.isIconTile(tiles[index].spec)) { - GridItemSpan(1) - } else { - GridItemSpan(2) - } - } - ) { index -> + items(tiles.size, span = { index -> GridItemSpan(tiles[index].spec.width()) }) { index + -> Tile( tile = tiles[index], iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec), @@ -92,4 +86,22 @@ constructor( onRemoveTile = onRemoveTile, ) } + + override fun splitIntoPages( + tiles: List<TileViewModel>, + rows: Int, + columns: Int, + ): List<List<TileViewModel>> { + + return PaginatableGridLayout.splitInRows( + tiles.map { SizedTile(it, it.spec.width()) }, + columns, + ) + .chunked(rows) + .map { it.flatten().map { it.tile } } + } + + private fun TileSpec.width(): Int { + return if (iconTilesViewModel.isIconTile(this)) 1 else 2 + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt new file mode 100644 index 000000000000..7de221612161 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.pager.PagerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.semantics.pageLeft +import androidx.compose.ui.semantics.pageRight +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlin.math.absoluteValue +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@Composable +fun PagerDots( + pagerState: PagerState, + activeColor: Color, + nonActiveColor: Color, + modifier: Modifier = Modifier, + dotSize: Dp = 6.dp, + spaceSize: Dp = 4.dp, +) { + if (pagerState.pageCount < 2) { + return + } + val inPageTransition by + remember(pagerState) { + derivedStateOf { + pagerState.currentPageOffsetFraction.absoluteValue > 0.01 && + !pagerState.isOverscrolling() + } + } + val coroutineScope = rememberCoroutineScope() + Row( + modifier = + modifier + .wrapContentWidth() + .pagerDotsSemantics( + pagerState, + coroutineScope, + ), + horizontalArrangement = spacedBy(spaceSize), + verticalAlignment = Alignment.CenterVertically + ) { + if (!inPageTransition) { + repeat(pagerState.pageCount) { i -> + // We use canvas directly to only invalidate the draw phase when the page is + // changing. + Canvas(Modifier.size(dotSize)) { + if (pagerState.currentPage == i) { + drawCircle(activeColor) + } else { + drawCircle(nonActiveColor) + } + } + } + } else { + val doubleDotWidth = dotSize * 2 + spaceSize + val cornerRadius = dotSize / 2 + val width by + animateDpAsState(targetValue = if (inPageTransition) doubleDotWidth else dotSize) + + fun DrawScope.drawDoubleRect() { + drawRoundRect( + color = activeColor, + size = Size(width.toPx(), dotSize.toPx()), + cornerRadius = CornerRadius(cornerRadius.toPx(), cornerRadius.toPx()) + ) + } + + repeat(pagerState.pageCount) { page -> + Canvas(Modifier.size(dotSize)) { + val withPrevious = pagerState.currentPageOffsetFraction < 0 + val ltr = layoutDirection == LayoutDirection.Ltr + if ( + withPrevious && page == (pagerState.currentPage - 1) || + !withPrevious && page == pagerState.currentPage + ) { + if (ltr) { + drawDoubleRect() + } + } else if ( + withPrevious && page == pagerState.currentPage || + !withPrevious && page == (pagerState.currentPage + 1) + ) { + if (!ltr) { + drawDoubleRect() + } + } else { + drawCircle(nonActiveColor) + } + } + } + } + } +} + +private fun Modifier.pagerDotsSemantics( + pagerState: PagerState, + coroutineScope: CoroutineScope, +): Modifier { + return then( + Modifier.semantics { + pageLeft { + if (pagerState.canScrollBackward) { + coroutineScope.launch { + pagerState.animateScrollToPage(pagerState.currentPage - 1) + } + true + } else { + false + } + } + pageRight { + if (pagerState.canScrollForward) { + coroutineScope.launch { + pagerState.animateScrollToPage(pagerState.currentPage + 1) + } + true + } else { + false + } + } + stateDescription = "Page ${pagerState.settledPage + 1} of ${pagerState.pageCount}" + } + ) +} + +private fun PagerState.isOverscrolling(): Boolean { + val position = currentPage + currentPageOffsetFraction + return position < 0 || position > pageCount - 1 +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt new file mode 100644 index 000000000000..2ee957e89f48 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType +import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight +import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing +import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.res.R +import javax.inject.Inject + +class PaginatedGridLayout +@Inject +constructor( + private val viewModel: PaginatedGridViewModel, + @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout, +) : GridLayout by delegateGridLayout { + @Composable + override fun TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + editModeStart: () -> Unit, + ) { + DisposableEffect(tiles) { + val token = Any() + tiles.forEach { it.startListening(token) } + onDispose { tiles.forEach { it.stopListening(token) } } + } + val columns by viewModel.columns.collectAsStateWithLifecycle() + val rows by viewModel.rows.collectAsStateWithLifecycle() + + val pages = + remember(tiles, columns, rows) { + delegateGridLayout.splitIntoPages(tiles, rows = rows, columns = columns) + } + + val pagerState = rememberPagerState(0) { pages.size } + + Column { + HorizontalPager( + state = pagerState, + modifier = Modifier, + pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp, + beyondViewportPageCount = 1, + verticalAlignment = Alignment.Top, + ) { + val page = pages[it] + + delegateGridLayout.TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) + } + Box( + modifier = Modifier.height(FooterHeight).fillMaxWidth(), + ) { + PagerDots( + pagerState = pagerState, + activeColor = MaterialTheme.colorScheme.primary, + nonActiveColor = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier.align(Alignment.Center) + ) + CompositionLocalProvider(value = LocalContentColor provides Color.White) { + IconButton( + onClick = editModeStart, + modifier = Modifier.align(Alignment.CenterEnd), + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = stringResource(id = R.string.qs_edit) + ) + } + } + } + } + } + + private object Dimensions { + val FooterHeight = 48.dp + val InterPageSpacing = 16.dp + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt index 9233e76f09e6..7f5e474f2fa7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.modifiers.background import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.PartitionedGridViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel @@ -63,9 +64,13 @@ import javax.inject.Inject @SysUISingleton class PartitionedGridLayout @Inject constructor(private val viewModel: PartitionedGridViewModel) : - GridLayout { + PaginatableGridLayout { @Composable - override fun TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { + override fun TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + editModeStart: () -> Unit, + ) { DisposableEffect(tiles) { val token = Any() tiles.forEach { it.startListening(token) } @@ -169,6 +174,20 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition } } + override fun splitIntoPages( + tiles: List<TileViewModel>, + rows: Int, + columns: Int, + ): List<List<TileViewModel>> { + val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) } + + val sizedLargeTiles = largeTiles.map { SizedTile(it, 2) } + val sizedSmallTiles = smallTiles.map { SizedTile(it, 1) } + val largeTilesRows = PaginatableGridLayout.splitInRows(sizedLargeTiles, columns) + val smallTilesRows = PaginatableGridLayout.splitInRows(sizedSmallTiles, columns) + return (largeTilesRows + smallTilesRows).chunked(rows).map { it.flatten().map { it.tile } } + } + @Composable private fun CurrentTiles( tiles: List<EditTileViewModel>, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt index 7f4e0a7047b8..4a9010270ac5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt @@ -30,8 +30,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel -import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.res.R @@ -42,13 +42,14 @@ class StretchedGridLayout @Inject constructor( private val iconTilesViewModel: IconTilesViewModel, - private val gridSizeViewModel: InfiniteGridSizeViewModel, + private val gridSizeViewModel: FixedColumnsSizeViewModel, ) : GridLayout { @Composable override fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, + editModeStart: () -> Unit, ) { DisposableEffect(tiles) { val token = Any() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index 2dab7c3d61ca..8c57d41b2123 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -23,9 +23,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable -fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { +fun TileGrid( + viewModel: TileGridViewModel, + modifier: Modifier = Modifier, + editModeStart: () -> Unit +) { val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) - gridLayout.TileGrid(tiles, modifier) + gridLayout.TileGrid(tiles, modifier, editModeStart) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModel.kt index a4ee58f0963c..865c86b4096d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModel.kt @@ -17,16 +17,16 @@ package com.android.systemui.qs.panels.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor +import com.android.systemui.qs.panels.domain.interactor.FixedColumnsSizeInteractor import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow -interface InfiniteGridSizeViewModel { +interface FixedColumnsSizeViewModel { val columns: StateFlow<Int> } @SysUISingleton -class InfiniteGridSizeViewModelImpl @Inject constructor(interactor: InfiniteGridSizeInteractor) : - InfiniteGridSizeViewModel { +class FixedColumnsSizeViewModelImpl @Inject constructor(interactor: FixedColumnsSizeInteractor) : + FixedColumnsSizeViewModel { override val columns: StateFlow<Int> = interactor.columns } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt new file mode 100644 index 000000000000..28bf47400c4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn + +@SysUISingleton +class PaginatedGridViewModel +@Inject +constructor( + iconTilesViewModel: IconTilesViewModel, + gridSizeViewModel: FixedColumnsSizeViewModel, + iconLabelVisibilityViewModel: IconLabelVisibilityViewModel, + paginatedGridInteractor: PaginatedGridInteractor, + @Application applicationScope: CoroutineScope, +) : + IconTilesViewModel by iconTilesViewModel, + FixedColumnsSizeViewModel by gridSizeViewModel, + IconLabelVisibilityViewModel by iconLabelVisibilityViewModel { + val rows = + paginatedGridInteractor.rows.stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + paginatedGridInteractor.defaultRows, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt index 730cf635972d..2049edbd544f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt @@ -24,9 +24,9 @@ class PartitionedGridViewModel @Inject constructor( iconTilesViewModel: IconTilesViewModel, - gridSizeViewModel: InfiniteGridSizeViewModel, + gridSizeViewModel: FixedColumnsSizeViewModel, iconLabelVisibilityViewModel: IconLabelVisibilityViewModel, ) : IconTilesViewModel by iconTilesViewModel, - InfiniteGridSizeViewModel by gridSizeViewModel, + FixedColumnsSizeViewModel by gridSizeViewModel, IconLabelVisibilityViewModel by iconLabelVisibilityViewModel diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt index b5bef9f6ebe8..88f716951001 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt @@ -41,12 +41,7 @@ constructor( override fun ProducerScope<AutoAddSignal>.getCallback(): CastController.Callback { return CastController.Callback { - val isCasting = - controller.castDevices.any { - it.state == CastController.CastDevice.STATE_CONNECTED || - it.state == CastController.CastDevice.STATE_CONNECTING - } - if (isCasting) { + if (controller.castDevices.any { it.isCasting }) { sendAdd() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index b1b67cf51d20..44c846b5c631 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -350,6 +350,10 @@ constructor( initialLongPressProperties?.width = width finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * width + + val deltaW = (LONG_PRESS_EFFECT_WIDTH_SCALE - 1f) * width + paddingForLaunch.left = -deltaW.toInt() / 2 + paddingForLaunch.right = deltaW.toInt() / 2 } private fun maybeUpdateLongPressEffectHeight(height: Float) { @@ -357,6 +361,10 @@ constructor( initialLongPressProperties?.height = height finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * height + + val deltaH = (LONG_PRESS_EFFECT_HEIGHT_SCALE - 1f) * height + paddingForLaunch.top = -deltaH.toInt() / 2 + paddingForLaunch.bottom = deltaH.toInt() / 2 } override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) { @@ -432,14 +440,16 @@ constructor( longPressEffect?.callback = object : QSLongPressEffect.Callback { - override fun onPrepareForLaunch() { - prepareForLaunch() - } - override fun onResetProperties() { resetLongPressEffectProperties() } + override fun onEffectFinishedReversing() { + // The long-press effect properties finished at the same starting point. + // This is the same as if the properties were reset + haveLongPressPropertiesBeenReset = true + } + override fun onStartAnimator() { if (longPressEffectAnimator?.isRunning != true) { longPressEffectAnimator = @@ -1043,6 +1053,7 @@ constructor( getOverlayColorForState(Tile.STATE_ACTIVE), Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive), ) + prepareForLaunch() } private fun changeCornerRadius(radius: Float) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 169cdc17c2c0..8a72e8db7216 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -60,7 +60,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel; import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository; import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastController.CastDevice; +import com.android.systemui.statusbar.policy.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DialogKt; @@ -193,14 +193,13 @@ public class CastTile extends QSTileImpl<BooleanState> { // case where multiple devices were active :-/. private boolean willPopDialog() { List<CastDevice> activeDevices = getActiveDevices(); - return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo); + return activeDevices.isEmpty() || (activeDevices.get(0).getTag() instanceof RouteInfo); } private List<CastDevice> getActiveDevices() { ArrayList<CastDevice> activeDevices = new ArrayList<>(); for (CastDevice device : mController.getCastDevices()) { - if (device.state == CastDevice.STATE_CONNECTED - || device.state == CastDevice.STATE_CONNECTING) { + if (device.isCasting()) { activeDevices.add(device); } } @@ -276,7 +275,7 @@ public class CastTile extends QSTileImpl<BooleanState> { // We always choose the first device that's in the CONNECTED state in the case where // multiple devices are CONNECTED at the same time. for (CastDevice device : devices) { - if (device.state == CastDevice.STATE_CONNECTED) { + if (device.getState() == CastDevice.CastState.Connected) { state.value = true; state.secondaryLabel = getDeviceName(device); state.stateDescription = state.stateDescription + "," @@ -284,7 +283,7 @@ public class CastTile extends QSTileImpl<BooleanState> { R.string.accessibility_cast_name, state.label); connecting = false; break; - } else if (device.state == CastDevice.STATE_CONNECTING) { + } else if (device.getState() == CastDevice.CastState.Connecting) { connecting = true; } } @@ -315,7 +314,7 @@ public class CastTile extends QSTileImpl<BooleanState> { } private String getDeviceName(CastDevice device) { - return device.name != null ? device.name + return device.getName() != null ? device.getName() : mContext.getString(R.string.quick_settings_cast_device_default_name); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index 70f3b847ce07..a3feb2b09da3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -46,6 +46,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.recordissue.IssueRecordingService import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate +import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC import com.android.systemui.recordissue.TraceurMessageSender import com.android.systemui.res.R import com.android.systemui.screenrecord.RecordingService @@ -197,8 +198,4 @@ constructor( expandedAccessibilityClassName = Switch::class.java.name } } - - companion object { - const val TILE_SPEC = "record_issue" - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 4715230a0958..284239ab1a78 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -47,6 +47,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.screenrecord.data.model.ScreenRecordModel; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -146,8 +147,9 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> if (isRecording) { state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop); } else if (isStarting) { - // round, since the timer isn't exact - int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000); + int countdown = + (int) ScreenRecordModel.Starting.Companion.toCountdownSeconds( + mMillisUntilFinished); state.secondaryLabel = String.format("%d...", countdown); } else { state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractor.kt new file mode 100644 index 000000000000..1af328e1e0a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractor.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.irecording + +import android.os.UserHandle +import com.android.systemui.Flags +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.recordissue.IssueRecordingState +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart + +class IssueRecordingDataInteractor +@Inject +constructor( + private val state: IssueRecordingState, + @Background private val bgCoroutineContext: CoroutineContext, +) : QSTileDataInteractor<IssueRecordingModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<IssueRecordingModel> = + conflatedCallbackFlow { + val listener = Runnable { trySend(IssueRecordingModel(state.isRecording)) } + state.addListener(listener) + awaitClose { state.removeListener(listener) } + } + .onStart { emit(IssueRecordingModel(state.isRecording)) } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(android.os.Build.IS_DEBUGGABLE && Flags.recordIssueQsTile()) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt new file mode 100644 index 000000000000..ff931b35567f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.irecording + +import android.content.res.Resources +import android.content.res.Resources.Theme +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +class IssueRecordingMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<IssueRecordingModel> { + override fun map(config: QSTileConfig, data: IssueRecordingModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + if (data.isRecording) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = resources.getString(R.string.qs_record_issue_stop) + icon = { Icon.Resource(R.drawable.qs_record_issue_icon_on, null) } + } else { + icon = { Icon.Resource(R.drawable.qs_record_issue_icon_off, null) } + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = resources.getString(R.string.qs_record_issue_start) + } + supportedActions = setOf(QSTileState.UserAction.CLICK) + contentDescription = "$label, $secondaryLabel" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingModel.kt new file mode 100644 index 000000000000..260729b6a868 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingModel.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.irecording + +@JvmInline value class IssueRecordingModel(val isRecording: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt new file mode 100644 index 000000000000..4971fefc8f57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.irecording + +import android.app.AlertDialog +import android.app.BroadcastOptions +import android.app.PendingIntent +import android.util.Log +import com.android.internal.jank.InteractionJankMonitor +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.recordissue.IssueRecordingService +import com.android.systemui.recordissue.RecordIssueDialogDelegate +import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC +import com.android.systemui.screenrecord.RecordingService +import com.android.systemui.settings.UserContextProvider +import com.android.systemui.statusbar.phone.KeyguardDismissUtil +import com.android.systemui.statusbar.policy.KeyguardStateController +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext + +private const val TAG = "IssueRecordingActionInteractor" + +class IssueRecordingUserActionInteractor +@Inject +constructor( + @Main private val mainCoroutineContext: CoroutineContext, + private val keyguardDismissUtil: KeyguardDismissUtil, + private val keyguardStateController: KeyguardStateController, + private val dialogTransitionAnimator: DialogTransitionAnimator, + private val panelInteractor: PanelInteractor, + private val userContextProvider: UserContextProvider, + private val delegateFactory: RecordIssueDialogDelegate.Factory, +) : QSTileUserActionInteractor<IssueRecordingModel> { + + override suspend fun handleInput(input: QSTileInput<IssueRecordingModel>) { + if (input.action is QSTileUserAction.Click) { + if (input.data.isRecording) { + stopIssueRecordingService() + } else { + withContext(mainCoroutineContext) { showPrompt(input.action.expandable) } + } + } else { + Log.v(TAG, "the RecordIssueTile doesn't handle ${input.action} events yet.") + } + } + + private fun showPrompt(expandable: Expandable?) { + val dialog: AlertDialog = + delegateFactory + .create { + startIssueRecordingService() + dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations() + panelInteractor.collapsePanels() + } + .createDialog() + val dismissAction = + ActivityStarter.OnDismissAction { + // We animate from the touched view only if we are not on the keyguard, given + // that if we are we will dismiss it which will also collapse the shade. + if (expandable != null && !keyguardStateController.isShowing) { + expandable + .dialogTransitionController( + DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, TILE_SPEC) + ) + ?.let { dialogTransitionAnimator.show(dialog, it) } ?: dialog.show() + } else { + dialog.show() + } + false + } + keyguardDismissUtil.executeWhenUnlocked(dismissAction, false, true) + } + + private fun startIssueRecordingService() = + PendingIntent.getForegroundService( + userContextProvider.userContext, + RecordingService.REQUEST_CODE, + IssueRecordingService.getStartIntent(userContextProvider.userContext), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + + private fun stopIssueRecordingService() = + PendingIntent.getService( + userContextProvider.userContext, + RecordingService.REQUEST_CODE, + IssueRecordingService.getStopIntent(userContextProvider.userContext), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt index 7446708cfebc..e74e77f29007 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt @@ -61,7 +61,7 @@ constructor( contentDescription = null ) icon = { loadedIcon } - val countDown = Math.floorDiv(data.millisUntilStarted + 500, 1000) + val countDown = data.countdownSeconds sideViewIcon = QSTileState.SideViewIcon.None secondaryLabel = String.format("%d...", countDown) } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt index 4ea334522ad4..b077349ade74 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt @@ -18,7 +18,7 @@ package com.android.systemui.recordissue import android.content.Context import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.tiles.RecordIssueTile +import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker @@ -35,11 +35,7 @@ constructor( ) { private val prefs = - userFileManager.getSharedPreferences( - RecordIssueTile.TILE_SPEC, - Context.MODE_PRIVATE, - userTracker.userId - ) + userFileManager.getSharedPreferences(TILE_SPEC, Context.MODE_PRIVATE, userTracker.userId) var takeBugreport get() = prefs.getBoolean(KEY_TAKE_BUG_REPORT, false) diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt index 26af9a71de7d..907b92ce4c9b 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt @@ -16,12 +16,20 @@ package com.android.systemui.recordissue +import com.android.systemui.Flags import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.RecordIssueTile +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.irecording.IssueRecordingDataInteractor +import com.android.systemui.qs.tiles.impl.irecording.IssueRecordingMapper +import com.android.systemui.qs.tiles.impl.irecording.IssueRecordingModel +import com.android.systemui.qs.tiles.impl.irecording.IssueRecordingUserActionInteractor import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel import com.android.systemui.res.R import dagger.Binds import dagger.Module @@ -34,19 +42,19 @@ interface RecordIssueModule { /** Inject RecordIssueTile into tileMap in QSModule */ @Binds @IntoMap - @StringKey(RecordIssueTile.TILE_SPEC) + @StringKey(TILE_SPEC) fun bindRecordIssueTile(recordIssueTile: RecordIssueTile): QSTileImpl<*> companion object { - const val RECORD_ISSUE_TILE_SPEC = "record_issue" + const val TILE_SPEC = "record_issue" @Provides @IntoMap - @StringKey(RECORD_ISSUE_TILE_SPEC) + @StringKey(TILE_SPEC) fun provideRecordIssueTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = QSTileConfig( - tileSpec = TileSpec.create(RECORD_ISSUE_TILE_SPEC), + tileSpec = TileSpec.create(TILE_SPEC), uiConfig = QSTileUIConfig.Resource( iconRes = R.drawable.qs_record_issue_icon_off, @@ -54,5 +62,24 @@ interface RecordIssueModule { ), instanceId = uiEventLogger.getNewInstanceId(), ) + + /** Inject FlashlightTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(TILE_SPEC) + fun provideIssueRecordingTileViewModel( + factory: QSTileViewModelFactory.Static<IssueRecordingModel>, + mapper: IssueRecordingMapper, + stateInteractor: IssueRecordingDataInteractor, + userActionInteractor: IssueRecordingUserActionInteractor + ): QSTileViewModel = + if (Flags.qsNewTilesFuture()) + factory.create( + TileSpec.create(TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + else StubQSTileViewModel } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt index 323ca871e8b0..6e8997346fc4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt @@ -23,8 +23,10 @@ import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInte import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolverModule import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule +import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartable import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.domain.startable.ScrimStartable +import com.android.systemui.scene.domain.startable.StatusBarStartable import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shared.flag.DualShade @@ -66,6 +68,16 @@ interface KeyguardlessSceneContainerFrameworkModule { @Binds @IntoMap + @ClassKey(StatusBarStartable::class) + fun statusBarStartable(impl: StatusBarStartable): CoreStartable + + @Binds + @IntoMap + @ClassKey(KeyguardStateCallbackStartable::class) + fun keyguardStateCallbackStartable(impl: KeyguardStateCallbackStartable): CoreStartable + + @Binds + @IntoMap @ClassKey(WindowRootViewVisibilityInteractor::class) fun bindWindowRootViewVisibilityInteractor( impl: WindowRootViewVisibilityInteractor diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 4691ebaf95b3..7d63b4ce0044 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -24,8 +24,10 @@ import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInte import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolverModule import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule +import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartable import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.domain.startable.ScrimStartable +import com.android.systemui.scene.domain.startable.StatusBarStartable import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shared.flag.DualShade @@ -72,6 +74,16 @@ interface SceneContainerFrameworkModule { @Binds @IntoMap + @ClassKey(StatusBarStartable::class) + fun statusBarStartable(impl: StatusBarStartable): CoreStartable + + @Binds + @IntoMap + @ClassKey(KeyguardStateCallbackStartable::class) + fun keyguardStateCallbackStartable(impl: KeyguardStateCallbackStartable): CoreStartable + + @Binds + @IntoMap @ClassKey(WindowRootViewVisibilityInteractor::class) fun bindWindowRootViewVisibilityInteractor( impl: WindowRootViewVisibilityInteractor diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 08175c37f9bb..25a9e9e6f9f2 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -22,6 +22,7 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.resolver.SceneResolver import com.android.systemui.scene.shared.logger.SceneLogger @@ -41,6 +42,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn /** @@ -59,6 +61,7 @@ constructor( private val logger: SceneLogger, private val sceneFamilyResolvers: Lazy<Map<SceneKey, @JvmSuppressWildcards SceneResolver>>, private val deviceUnlockedInteractor: DeviceUnlockedInteractor, + private val keyguardEnabledInteractor: KeyguardEnabledInteractor, ) { interface OnSceneAboutToChangeListener { @@ -102,7 +105,14 @@ constructor( * 2. When transitioning, which scenes are being transitioned between. * 3. When transitioning, what the progress of the transition is. */ - val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState + val transitionState: StateFlow<ObservableTransitionState> = + repository.transitionState + .onEach { logger.logSceneTransition(it) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = repository.transitionState.value, + ) /** * The key of the scene that the UI is currently transitioning to or `null` if there is no @@ -373,7 +383,8 @@ constructor( val isChangeAllowed = to != Scenes.Gone || inMidTransitionFromGone || - deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked + deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked || + !keyguardEnabledInteractor.isKeyguardEnabled.value check(isChangeAllowed) { "Cannot change to the Gone scene while the device is locked and not currently" + " transitioning from Gone. Current transition state is ${transitionState.value}." + diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt new file mode 100644 index 000000000000..6d1c1a7b5bfe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import android.os.DeadObjectException +import android.os.RemoteException +import com.android.internal.policy.IKeyguardStateCallback +import com.android.systemui.CoreStartable +import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.keyguard.domain.interactor.TrustInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** Keeps all [IKeyguardStateCallback]s hydrated with the latest state. */ +@SysUISingleton +class KeyguardStateCallbackStartable +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val sceneInteractor: SceneInteractor, + private val selectedUserInteractor: SelectedUserInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, + private val simBouncerInteractor: SimBouncerInteractor, + private val trustInteractor: TrustInteractor, +) : CoreStartable { + + private val callbacks = mutableListOf<IKeyguardStateCallback>() + + override fun start() { + if (!SceneContainerFlag.isEnabled) { + return + } + + hydrateKeyguardShowingAndInputRestrictionStates() + hydrateSimSecureState() + notifyWhenKeyguardShowingChanged() + notifyWhenTrustChanged() + } + + fun addCallback(callback: IKeyguardStateCallback) { + SceneContainerFlag.assertInNewMode() + + callbacks.add(callback) + + applicationScope.launch(backgroundDispatcher) { + callback.onShowingStateChanged( + !deviceEntryInteractor.isDeviceEntered.value, + selectedUserInteractor.getSelectedUserId(), + ) + callback.onTrustedChanged(trustInteractor.isTrusted.value) + callback.onSimSecureStateChanged(simBouncerInteractor.isAnySimSecure.value) + // TODO(b/348644111): add support for mNeedToReshowWhenReenabled + callback.onInputRestrictedStateChanged(!deviceEntryInteractor.isDeviceEntered.value) + } + } + + private fun hydrateKeyguardShowingAndInputRestrictionStates() { + applicationScope.launch { + combine( + selectedUserInteractor.selectedUser, + deviceEntryInteractor.isDeviceEntered, + ::Pair + ) + .collectLatest { (selectedUserId, isDeviceEntered) -> + val iterator = callbacks.iterator() + withContext(backgroundDispatcher) { + while (iterator.hasNext()) { + val callback = iterator.next() + try { + callback.onShowingStateChanged(!isDeviceEntered, selectedUserId) + // TODO(b/348644111): add support for mNeedToReshowWhenReenabled + callback.onInputRestrictedStateChanged(!isDeviceEntered) + } catch (e: RemoteException) { + if (e is DeadObjectException) { + iterator.remove() + } + } + } + } + } + } + } + + private fun hydrateSimSecureState() { + applicationScope.launch { + simBouncerInteractor.isAnySimSecure.collectLatest { isSimSecured -> + val iterator = callbacks.iterator() + withContext(backgroundDispatcher) { + while (iterator.hasNext()) { + val callback = iterator.next() + try { + callback.onSimSecureStateChanged(isSimSecured) + } catch (e: RemoteException) { + if (e is DeadObjectException) { + iterator.remove() + } + } + } + } + } + } + } + + private fun notifyWhenKeyguardShowingChanged() { + applicationScope.launch { + // This is equivalent to isDeviceEntered but it waits for the full transition animation + // to finish before emitting a new value and not just for the current scene to be + // switched. + sceneInteractor.transitionState + .filter { it.isIdle(Scenes.Gone) || it.isIdle(Scenes.Lockscreen) } + .map { it.isIdle(Scenes.Lockscreen) } + .distinctUntilChanged() + .collectLatest { trustInteractor.reportKeyguardShowingChanged() } + } + } + + private fun notifyWhenTrustChanged() { + applicationScope.launch { + trustInteractor.isTrusted.collectLatest { isTrusted -> + val iterator = callbacks.iterator() + withContext(backgroundDispatcher) { + while (iterator.hasNext()) { + val callback = iterator.next() + try { + callback.onTrustedChanged(isTrusted) + } catch (e: RemoteException) { + if (e is DeadObjectException) { + iterator.remove() + } + } + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 1e689bd11fa3..218853d7a1fb 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -22,6 +22,7 @@ import android.app.StatusBarManager import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.UiEventLogger +import com.android.internal.policy.IKeyguardStateCallback import com.android.systemui.CoreStartable import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel @@ -127,6 +128,8 @@ constructor( private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() + private val keyguardStateCallbacks = mutableListOf<IKeyguardStateCallback>() + override fun start() { if (SceneContainerFlag.isEnabled) { sceneLogger.logFrameworkEnabled(isEnabled = true) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt new file mode 100644 index 000000000000..893f030fab4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import android.annotation.SuppressLint +import android.app.StatusBarManager +import android.content.Context +import android.os.Binder +import android.os.IBinder +import android.os.RemoteException +import android.provider.DeviceConfig +import android.util.Log +import com.android.compose.animation.scene.SceneKey +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.CoreStartable +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceconfig.domain.interactor.DeviceConfigInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.navigation.domain.interactor.NavigationInteractor +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessModel +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@SysUISingleton +class StatusBarStartable +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + @Application private val applicationContext: Context, + private val selectedUserInteractor: SelectedUserInteractor, + private val sceneInteractor: SceneInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, + private val sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor, + private val deviceConfigInteractor: DeviceConfigInteractor, + private val navigationInteractor: NavigationInteractor, + private val authenticationInteractor: AuthenticationInteractor, + private val powerInteractor: PowerInteractor, + private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, + private val statusBarService: IStatusBarService, +) : CoreStartable { + + private val disableToken: IBinder = Binder() + + override fun start() { + if (!SceneContainerFlag.isEnabled) { + return + } + + applicationScope.launch { + combine( + selectedUserInteractor.selectedUser, + sceneInteractor.currentScene, + deviceEntryInteractor.isDeviceEntered, + sceneContainerOcclusionInteractor.invisibleDueToOcclusion, + deviceConfigInteractor.property( + namespace = DeviceConfig.NAMESPACE_SYSTEMUI, + name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, + default = true, + ), + navigationInteractor.isGesturalMode, + authenticationInteractor.authenticationMethod, + powerInteractor.detailedWakefulness, + ) { values -> + val selectedUserId = values[0] as Int + val currentScene = values[1] as SceneKey + val isDeviceEntered = values[2] as Boolean + val isOccluded = values[3] as Boolean + val isShowHomeOverLockscreen = values[4] as Boolean + val isGesturalMode = values[5] as Boolean + val authenticationMethod = values[6] as AuthenticationMethodModel + val wakefulnessModel = values[7] as WakefulnessModel + + val isForceHideHomeAndRecents = currentScene == Scenes.Bouncer + val isKeyguardShowing = !isDeviceEntered + val isPowerGestureIntercepted = + with(wakefulnessModel) { + isAwake() && + powerButtonLaunchGestureTriggered && + lastSleepReason == WakeSleepReason.POWER_BUTTON + } + + var flags = StatusBarManager.DISABLE_NONE + + if (isForceHideHomeAndRecents || (isKeyguardShowing && !isOccluded)) { + if (!isShowHomeOverLockscreen || !isGesturalMode) { + flags = flags or StatusBarManager.DISABLE_HOME + } + flags = flags or StatusBarManager.DISABLE_RECENT + } + + if ( + isPowerGestureIntercepted && + isOccluded && + authenticationMethod.isSecure && + deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled() + ) { + flags = flags or StatusBarManager.DISABLE_RECENT + } + + selectedUserId to flags + } + .distinctUntilChanged() + .collect { (selectedUserId, flags) -> + @SuppressLint("WrongConstant", "NonInjectedService") + if (applicationContext.getSystemService(Context.STATUS_BAR_SERVICE) == null) { + Log.w(TAG, "Could not get status bar manager") + return@collect + } + + withContext(backgroundDispatcher) { + try { + statusBarService.disableForUser( + flags, + disableToken, + applicationContext.packageName, + selectedUserId, + ) + } catch (e: RemoteException) { + Log.d(TAG, "Failed to set disable flags: $flags", e) + } + } + } + } + } + + override fun onBootCompleted() { + applicationScope.launch(backgroundDispatcher) { + try { + statusBarService.disableForUser( + StatusBarManager.DISABLE_NONE, + disableToken, + applicationContext.packageName, + selectedUserInteractor.getSelectedUserId(true), + ) + } catch (e: RemoteException) { + Log.d(TAG, "Failed to clear flags", e) + } + } + } + + companion object { + private const val TAG = "StatusBarStartable" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index cf33c4a3fafb..6c63c9762e5e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -39,13 +39,14 @@ object SceneContainerFlag { inline val isEnabled get() = sceneContainer() && // mainAconfigFlag - ComposeLockscreen.isEnabled && + ComposeLockscreen.isEnabled && KeyguardBottomAreaRefactor.isEnabled && KeyguardWmStateRefactor.isEnabled && MigrateClocksToBlueprint.isEnabled && NotificationsHeadsUpRefactor.isEnabled && PredictiveBackSysUiFlag.isEnabled && DeviceEntryUdfpsRefactor.isEnabled + // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer /** The main aconfig flag. */ @@ -91,6 +92,14 @@ object SceneContainerFlag { @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION) + /** + * Called to ensure the new code is only run when the flag is enabled. This will throw an + * exception if the flag is disabled to ensure that the refactor author catches issues in + * testing. + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, DESCRIPTION) + /** Returns a developer-readable string that describes the current requirement list. */ @JvmStatic fun requirementDescription(): String { diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index 9d6720b02052..cf1518ea2b8c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene.shared.logger +import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel @@ -84,6 +85,30 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } + fun logSceneTransition(transitionState: ObservableTransitionState) { + when (transitionState) { + is ObservableTransitionState.Transition -> { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = transitionState.fromScene.toString() + str2 = transitionState.toScene.toString() + }, + messagePrinter = { "Scene transition started: $str1 → $str2" }, + ) + } + is ObservableTransitionState.Idle -> { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { str1 = transitionState.currentScene.toString() }, + messagePrinter = { "Scene transition idle on: $str1" }, + ) + } + } + } + fun logVisibilityChange( from: Boolean, to: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt index ef393e4858e2..be954410fd2d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt @@ -31,6 +31,11 @@ object TransitionKeys { val CollapseShadeInstantly = TransitionKey("CollapseShadeInstantly") /** + * Reference to a scene transition that brings up the shade from the bottom instead of the top. + */ + val OpenBottomShade = TransitionKey("OpenBottomShade") + + /** * Reference to a scene transition that can collapse the shade scene slightly faster than a * normal collapse would. */ diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt index c34a6cdf17df..9f48ee9e5ecb 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene.ui.viewmodel +import androidx.compose.ui.Alignment import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection @@ -24,6 +25,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.SceneFamilies +import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode @@ -39,7 +41,7 @@ class GoneSceneViewModel @Inject constructor( @Application private val applicationScope: CoroutineScope, - shadeInteractor: ShadeInteractor, + private val shadeInteractor: ShadeInteractor, ) { val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = shadeInteractor.shadeMode @@ -62,23 +64,38 @@ constructor( // TODO(b/338577208): Remove this once we add Dual Shade invocation zones. shadeMode is ShadeMode.Dual ) { - put( - Swipe( - pointerCount = 2, - fromSource = Edge.Top, - direction = SwipeDirection.Down, - ), - UserActionResult(SceneFamilies.QuickSettings) - ) + if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) { + put( + Swipe( + pointerCount = 2, + fromSource = Edge.Bottom, + direction = SwipeDirection.Up, + ), + UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade) + ) + } else { + put( + Swipe( + pointerCount = 2, + fromSource = Edge.Top, + direction = SwipeDirection.Down, + ), + UserActionResult(SceneFamilies.QuickSettings) + ) + } } - put( - Swipe(direction = SwipeDirection.Down), - UserActionResult( - SceneFamilies.NotifShade, - ToSplitShade.takeIf { shadeMode is ShadeMode.Split } + if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) { + put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade)) + } else { + put( + Swipe.Down, + UserActionResult( + SceneFamilies.NotifShade, + ToSplitShade.takeIf { shadeMode is ShadeMode.Split } + ) ) - ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt index b225444a1c06..ada5d8c06efc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/data/model/ScreenRecordModel.kt @@ -22,7 +22,17 @@ sealed interface ScreenRecordModel { data object Recording : ScreenRecordModel /** A screen recording will begin in [millisUntilStarted] ms. */ - data class Starting(val millisUntilStarted: Long) : ScreenRecordModel + data class Starting(val millisUntilStarted: Long) : ScreenRecordModel { + val countdownSeconds = millisUntilStarted.toCountdownSeconds() + + companion object { + /** + * Returns the number of seconds until screen recording will start, used to show a 3-2-1 + * countdown. + */ + fun Long.toCountdownSeconds() = Math.floorDiv(this + 500, 1000) + } + } /** There's nothing related to screen recording happening. */ data object DoingNothing : ScreenRecordModel diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt new file mode 100644 index 000000000000..6730d2d86d5f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.net.Uri +import android.os.UserManager +import android.util.Log +import android.view.WindowManager +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.google.common.util.concurrent.ListenableFuture +import java.util.UUID +import java.util.concurrent.Executor +import java.util.concurrent.Executors +import java.util.function.Consumer +import javax.inject.Inject + +/** + * A ScreenshotHandler that just saves the screenshot and calls back as appropriate, with no UI. + * + * Basically, ScreenshotController with all the UI bits ripped out. + */ +class HeadlessScreenshotHandler +@Inject +constructor( + private val imageExporter: ImageExporter, + @Main private val mainExecutor: Executor, + private val imageCapture: ImageCapture, + private val userManager: UserManager, + private val uiEventLogger: UiEventLogger, + private val notificationsControllerFactory: ScreenshotNotificationsController.Factory, +) : ScreenshotHandler { + + override fun handleScreenshot( + screenshot: ScreenshotData, + finisher: Consumer<Uri?>, + requestCallback: TakeScreenshotService.RequestCallback + ) { + if (screenshot.type == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) { + screenshot.bitmap = imageCapture.captureDisplay(screenshot.displayId, crop = null) + } + + if (screenshot.bitmap == null) { + Log.e(TAG, "handleScreenshot: Screenshot bitmap was null") + notificationsControllerFactory + .create(screenshot.displayId) + .notifyScreenshotError(R.string.screenshot_failed_to_capture_text) + requestCallback.reportError() + return + } + + val future: ListenableFuture<ImageExporter.Result> = + imageExporter.export( + Executors.newSingleThreadExecutor(), + UUID.randomUUID(), + screenshot.bitmap, + screenshot.getUserOrDefault(), + screenshot.displayId + ) + future.addListener( + { + try { + val result = future.get() + Log.d(TAG, "Saved screenshot: $result") + logScreenshotResultStatus(result.uri, screenshot) + finisher.accept(result.uri) + requestCallback.onFinish() + } catch (e: Exception) { + Log.d(TAG, "Failed to store screenshot", e) + finisher.accept(null) + requestCallback.reportError() + } + }, + mainExecutor + ) + } + + private fun logScreenshotResultStatus(uri: Uri?, screenshot: ScreenshotData) { + if (uri == null) { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, screenshot.packageNameString) + notificationsControllerFactory + .create(screenshot.displayId) + .notifyScreenshotError(R.string.screenshot_failed_to_save_text) + } else { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, screenshot.packageNameString) + if (userManager.isManagedProfile(screenshot.getUserOrDefault().identifier)) { + uiEventLogger.log( + ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, + 0, + screenshot.packageNameString + ) + } + } + } + + companion object { + const val TAG = "HeadlessScreenshotHandler" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt index 2ffb7835f400..5f1688650cb2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt @@ -18,6 +18,7 @@ package com.android.systemui.screenshot import android.app.assist.AssistContent import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance +import com.android.systemui.screenshot.ui.viewmodel.PreviewAction import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -84,9 +85,9 @@ constructor( } inner class ActionsCallback(private val screenshotId: UUID) { - fun providePreviewAction(onClick: () -> Unit) { + fun providePreviewAction(previewAction: PreviewAction) { if (screenshotId == currentScreenshotId) { - viewModel.setPreviewAction(onClick) + viewModel.setPreviewAction(previewAction) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index b8029c8b1cc3..c216f1da2dc1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -29,6 +29,7 @@ import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance +import com.android.systemui.screenshot.ui.viewmodel.PreviewAction import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -40,7 +41,9 @@ import java.util.UUID */ interface ScreenshotActionsProvider { fun onScrollChipReady(onClick: Runnable) + fun onScrollChipInvalidated() + fun setCompletedScreenshot(result: ScreenshotSavedResult) /** @@ -75,17 +78,19 @@ constructor( private var result: ScreenshotSavedResult? = null init { - actionsCallback.providePreviewAction { - debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" } - uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString) - onDeferrableActionTapped { result -> - actionExecutor.startSharedTransition( - createEdit(result.uri, context), - result.user, - true - ) + actionsCallback.providePreviewAction( + PreviewAction(context.resources.getString(R.string.screenshot_edit_description)) { + debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" } + uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString) + onDeferrableActionTapped { result -> + actionExecutor.startSharedTransition( + createEdit(result.uri, context), + result.user, + true + ) + } } - } + ) actionsCallback.provideActionButton( ActionButtonAppearance( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index e8dfac868546..c87b1f526957 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -101,7 +101,7 @@ import javax.inject.Provider; /** * Controls the state and flow for screenshots. */ -public class ScreenshotController { +public class ScreenshotController implements ScreenshotHandler { private static final String TAG = logTag(ScreenshotController.class); /** @@ -351,7 +351,8 @@ public class ScreenshotController { mShowUIOnExternalDisplay = showUIOnExternalDisplay; } - void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, + @Override + public void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, RequestCallback requestCallback) { Assert.isMainThread(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 40d709d0ab25..2699657847b9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.screenshot import android.net.Uri @@ -12,6 +28,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.res.R import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback import java.util.function.Consumer import javax.inject.Inject @@ -25,9 +42,13 @@ interface TakeScreenshotExecutor { onSaved: (Uri?) -> Unit, requestCallback: RequestCallback ) + fun onCloseSystemDialogsReceived() + fun removeWindows() + fun onDestroy() + fun executeScreenshotsAsync( screenshotRequest: ScreenshotRequest, onSaved: Consumer<Uri?>, @@ -35,6 +56,14 @@ interface TakeScreenshotExecutor { ) } +interface ScreenshotHandler { + fun handleScreenshot( + screenshot: ScreenshotData, + finisher: Consumer<Uri?>, + requestCallback: RequestCallback + ) +} + /** * Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the * result. @@ -51,10 +80,10 @@ constructor( private val screenshotRequestProcessor: ScreenshotRequestProcessor, private val uiEventLogger: UiEventLogger, private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory, + private val headlessScreenshotHandler: HeadlessScreenshotHandler, ) : TakeScreenshotExecutor { - private val displays = displayRepository.displays - private val screenshotControllers = mutableMapOf<Int, ScreenshotController>() + private var screenshotController: ScreenshotController? = null private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>() /** @@ -72,9 +101,15 @@ constructor( val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback) displays.forEach { display -> val displayId = display.displayId + var screenshotHandler: ScreenshotHandler = + if (displayId == Display.DEFAULT_DISPLAY) { + getScreenshotController(display) + } else { + headlessScreenshotHandler + } Log.d(TAG, "Executing screenshot for display $displayId") dispatchToController( - display = display, + screenshotHandler, rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId), onSaved = if (displayId == Display.DEFAULT_DISPLAY) { @@ -87,7 +122,7 @@ constructor( /** All logging should be triggered only by this method. */ private suspend fun dispatchToController( - display: Display, + screenshotHandler: ScreenshotHandler, rawScreenshotData: ScreenshotData, onSaved: (Uri?) -> Unit, callback: RequestCallback @@ -101,13 +136,12 @@ constructor( logScreenshotRequested(rawScreenshotData) onFailedScreenshotRequest(rawScreenshotData, callback) } - .getOrNull() - ?: return + .getOrNull() ?: return logScreenshotRequested(screenshotData) Log.d(TAG, "Screenshot request: $screenshotData") try { - getScreenshotController(display).handleScreenshot(screenshotData, onSaved, callback) + screenshotHandler.handleScreenshot(screenshotData, onSaved, callback) } catch (e: IllegalStateException) { Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e) onFailedScreenshotRequest(screenshotData, callback) @@ -140,43 +174,31 @@ constructor( private suspend fun getDisplaysToScreenshot(requestType: Int): List<Display> { val allDisplays = displays.first() return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) { - // If this is a provided image, let's show the UI on the default display only. + // If this is a provided image just screenshot th default display allDisplays.filter { it.displayId == Display.DEFAULT_DISPLAY } } else { allDisplays.filter { it.type in ALLOWED_DISPLAY_TYPES } } } - /** Propagates the close system dialog signal to all controllers. */ + /** Propagates the close system dialog signal to the ScreenshotController. */ override fun onCloseSystemDialogsReceived() { - screenshotControllers.forEach { (_, screenshotController) -> - if (!screenshotController.isPendingSharedTransition) { - screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) - } + if (screenshotController?.isPendingSharedTransition == false) { + screenshotController?.requestDismissal(SCREENSHOT_DISMISSED_OTHER) } } /** Removes all screenshot related windows. */ override fun removeWindows() { - screenshotControllers.forEach { (_, screenshotController) -> - screenshotController.removeWindow() - } + screenshotController?.removeWindow() } /** * Destroys the executor. Afterwards, this class is not expected to work as intended anymore. */ override fun onDestroy() { - screenshotControllers.forEach { (_, screenshotController) -> - screenshotController.onDestroy() - } - screenshotControllers.clear() - } - - private fun getScreenshotController(display: Display): ScreenshotController { - return screenshotControllers.computeIfAbsent(display.displayId) { - screenshotControllerFactory.create(display, /* showUIOnExternalDisplay= */ false) - } + screenshotController?.onDestroy() + screenshotController = null } private fun getNotificationController(id: Int): ScreenshotNotificationsController { @@ -196,6 +218,12 @@ constructor( } } + private fun getScreenshotController(display: Display): ScreenshotController { + val controller = screenshotController ?: screenshotControllerFactory.create(display, false) + screenshotController = controller + return controller + } + /** * Returns a [RequestCallback] that wraps [originalCallback]. * diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java index d87d85b93b9d..59b47dc8d0ab 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java @@ -16,16 +16,20 @@ package com.android.systemui.screenshot.appclips; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED; import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_CANCELLED; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME; +import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_TASK_ID; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.PERMISSION_SELF; import android.app.Activity; import android.content.BroadcastReceiver; +import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -43,6 +47,7 @@ import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; +import android.widget.TextView; import androidx.activity.ComponentActivity; import androidx.annotation.Nullable; @@ -51,10 +56,13 @@ import androidx.lifecycle.ViewModelProvider; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger.UiEventEnum; import com.android.settingslib.Utils; +import com.android.systemui.Flags; import com.android.systemui.res.R; import com.android.systemui.screenshot.scroll.CropView; import com.android.systemui.settings.UserTracker; +import java.util.Set; + import javax.inject.Inject; /** @@ -73,8 +81,6 @@ import javax.inject.Inject; * * <p>This {@link Activity} runs in its own separate process to isolate memory intensive image * editing from SysUI process. - * - * TODO(b/267309532): Polish UI and animations. */ public class AppClipsActivity extends ComponentActivity { @@ -94,6 +100,7 @@ public class AppClipsActivity extends ComponentActivity { private CropView mCropView; private Button mSave; private Button mCancel; + private TextView mBacklinksData; private AppClipsViewModel mViewModel; private ResultReceiver mResultReceiver; @@ -153,11 +160,10 @@ public class AppClipsActivity extends ComponentActivity { mCancel = mLayout.findViewById(R.id.cancel); mSave.setOnClickListener(this::onClick); mCancel.setOnClickListener(this::onClick); - - mCropView = mLayout.findViewById(R.id.crop_view); - + mBacklinksData = mLayout.findViewById(R.id.backlinks_data); mPreview = mLayout.findViewById(R.id.preview); + mPreview.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> updateImageDimensions()); @@ -166,9 +172,19 @@ public class AppClipsActivity extends ComponentActivity { mViewModel.getScreenshot().observe(this, this::setScreenshot); mViewModel.getResultLiveData().observe(this, this::setResultThenFinish); mViewModel.getErrorLiveData().observe(this, this::setErrorThenFinish); + mViewModel.getBacklinksLiveData().observe(this, this::setBacklinksData); if (savedInstanceState == null) { - mViewModel.performScreenshot(); + int displayId = getDisplayId(); + mViewModel.performScreenshot(displayId); + + if (Flags.appClipsBacklinks()) { + int appClipsTaskId = getTaskId(); + int callingPackageTaskId = intent.getIntExtra(EXTRA_CALLING_PACKAGE_TASK_ID, + INVALID_TASK_ID); + Set<Integer> taskIdsToIgnore = Set.of(appClipsTaskId, callingPackageTaskId); + mViewModel.triggerBacklinks(taskIdsToIgnore, displayId); + } } } @@ -281,6 +297,15 @@ public class AppClipsActivity extends ComponentActivity { finish(); } + private void setBacklinksData(ClipData clipData) { + if (mBacklinksData.getVisibility() == View.GONE) { + mBacklinksData.setVisibility(View.VISIBLE); + } + + mBacklinksData.setText(String.format(getString(R.string.backlinks_string), + clipData.getDescription().getLabel())); + } + private void setError(int errorCode) { if (mResultReceiver == null) { return; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java index 7de22b1a9c77..aaa5dfc01ba3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.os.UserHandle; +import android.util.Log; import androidx.annotation.Nullable; @@ -27,19 +28,18 @@ import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; -import com.android.systemui.settings.DisplayTracker; import javax.inject.Inject; /** An intermediary singleton object to help communicating with the cross process service. */ @SysUISingleton class AppClipsCrossProcessHelper { + private static final String TAG = AppClipsCrossProcessHelper.class.getSimpleName(); private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector; - private final DisplayTracker mDisplayTracker; @Inject - AppClipsCrossProcessHelper(@Application Context context, DisplayTracker displayTracker) { + AppClipsCrossProcessHelper(@Application Context context) { // Start a service as main user so that even if the app clips activity is running as work // profile user the service is able to use correct instance of Bubbles to grab a screenshot // excluding the bubble layer. @@ -48,7 +48,6 @@ class AppClipsCrossProcessHelper { Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | Context.BIND_NOT_VISIBLE, UserHandle.USER_SYSTEM, IAppClipsScreenshotHelperService.Stub::asInterface); - mDisplayTracker = displayTracker; } /** @@ -58,15 +57,16 @@ class AppClipsCrossProcessHelper { * pass around but not a {@link Bitmap}. */ @Nullable - Bitmap takeScreenshot() { + Bitmap takeScreenshot(int displayId) { try { AndroidFuture<ScreenshotHardwareBufferInternal> future = mProxyConnector.postForResult( - service -> - // Take a screenshot of the default display of the user. - service.takeScreenshot(mDisplayTracker.getDefaultDisplayId())); + service -> service.takeScreenshot(displayId)); return future.get().createBitmapThenCloseBuffer(); } catch (Exception e) { + Log.e(TAG, + String.format("Error while capturing a screenshot of displayId %d", displayId), + e); return null; } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java index 48449b393903..3c4469d052b1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java @@ -85,6 +85,7 @@ public class AppClipsTrampolineActivity extends Activity { static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE"; static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; + static final String EXTRA_CALLING_PACKAGE_TASK_ID = TAG + "CALLING_PACKAGE_TASK_ID"; private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0); private final NoteTaskController mNoteTaskController; @@ -193,12 +194,14 @@ public class AppClipsTrampolineActivity extends Activity { ComponentName componentName = ComponentName.unflattenFromString( getString(R.string.config_screenshotAppClipsActivityComponent)); String callingPackageName = getCallingPackage(); + int callingPackageTaskId = getTaskId(); Intent intent = new Intent() .setComponent(componentName) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver) - .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName); + .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName) + .putExtra(EXTRA_CALLING_PACKAGE_TASK_ID, callingPackageTaskId); try { startActivity(intent); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java index 630d3380f4d7..9bb7bbfbe4c3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java @@ -16,9 +16,23 @@ package com.android.systemui.screenshot.appclips; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.ACTION_VIEW; import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED; +import static android.content.Intent.CATEGORY_LAUNCHER; +import static com.google.common.util.concurrent.Futures.withTimeout; + +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; + +import android.app.ActivityTaskManager.RootTaskInfo; +import android.app.IActivityTaskManager; +import android.app.WindowConfiguration; +import android.app.assist.AssistContent; +import android.content.ClipData; +import android.content.ComponentName; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; import android.graphics.RecordingCanvas; @@ -26,10 +40,13 @@ import android.graphics.Rect; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.RemoteException; import android.os.UserHandle; +import android.util.Log; import android.view.Display; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; @@ -37,22 +54,36 @@ import androidx.lifecycle.ViewModelProvider; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.screenshot.AssistContentRequester; import com.android.systemui.screenshot.ImageExporter; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import java.util.Collections; +import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; /** A {@link ViewModel} to help with the App Clips screenshot flow. */ final class AppClipsViewModel extends ViewModel { + private static final String TAG = AppClipsViewModel.class.getSimpleName(); + private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper; private final ImageExporter mImageExporter; + private final IActivityTaskManager mAtmService; + private final AssistContentRequester mAssistContentRequester; + private final PackageManager mPackageManager; + @Main private final Executor mMainExecutor; @Background @@ -61,24 +92,34 @@ final class AppClipsViewModel extends ViewModel { private final MutableLiveData<Bitmap> mScreenshotLiveData; private final MutableLiveData<Uri> mResultLiveData; private final MutableLiveData<Integer> mErrorLiveData; + private final MutableLiveData<ClipData> mBacklinksLiveData; - AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper, - ImageExporter imageExporter, @Main Executor mainExecutor, - @Background Executor bgExecutor) { + private AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper, + ImageExporter imageExporter, IActivityTaskManager atmService, + AssistContentRequester assistContentRequester, PackageManager packageManager, + @Main Executor mainExecutor, @Background Executor bgExecutor) { mAppClipsCrossProcessHelper = appClipsCrossProcessHelper; mImageExporter = imageExporter; + mAtmService = atmService; + mAssistContentRequester = assistContentRequester; + mPackageManager = packageManager; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mScreenshotLiveData = new MutableLiveData<>(); mResultLiveData = new MutableLiveData<>(); mErrorLiveData = new MutableLiveData<>(); + mBacklinksLiveData = new MutableLiveData<>(); } - /** Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link LiveData}. */ - void performScreenshot() { + /** + * Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link #getScreenshot()}. + * + * @param displayId id of the {@link Display} to capture screenshot. + */ + void performScreenshot(int displayId) { mBgExecutor.execute(() -> { - Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot(); + Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot(displayId); mMainExecutor.execute(() -> { if (screenshot == null) { mErrorLiveData.setValue(CAPTURE_CONTENT_FOR_NOTE_FAILED); @@ -89,6 +130,38 @@ final class AppClipsViewModel extends ViewModel { }); } + /** + * Triggers the Backlinks flow which: + * <ul> + * <li>Evaluates the task to query. + * <li>Requests {@link AssistContent} from that task. + * <li>Transforms the {@link AssistContent} into {@link ClipData} for Backlinks. + * <li>The {@link ClipData} is reported to activity via {@link #getBacklinksLiveData()}. + * </ul> + * + * @param taskIdsToIgnore id of the tasks to ignore when querying for {@link AssistContent} + * @param displayId id of the display to query tasks for Backlinks data + */ + void triggerBacklinks(Set<Integer> taskIdsToIgnore, int displayId) { + mBgExecutor.execute(() -> { + ListenableFuture<ClipData> backlinksData = getBacklinksData(taskIdsToIgnore, displayId); + Futures.addCallback(backlinksData, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable ClipData result) { + if (result != null) { + mBacklinksLiveData.setValue(result); + } + } + + @Override + public void onFailure(Throwable t) { + Log.e(TAG, "Error querying for Backlinks data", t); + } + }, mMainExecutor); + + }); + } + /** Returns a {@link LiveData} that holds the captured screenshot. */ LiveData<Bitmap> getScreenshot() { return mScreenshotLiveData; @@ -107,6 +180,11 @@ final class AppClipsViewModel extends ViewModel { return mErrorLiveData; } + /** Returns a {@link LiveData} that holds the Backlinks data in {@link ClipData}. */ + LiveData<ClipData> getBacklinksLiveData() { + return mBacklinksLiveData; + } + /** * Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to * {@link LiveData}. @@ -148,21 +226,144 @@ final class AppClipsViewModel extends ViewModel { return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height()); } + private ListenableFuture<ClipData> getBacklinksData(Set<Integer> taskIdsToIgnore, + int displayId) { + return getAllRootTaskInfosOnDisplay(displayId) + .stream() + .filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore)) + .findFirst() + .map(this::getBacklinksDataForTaskId) + .orElse(Futures.immediateFuture(null)); + } + + private List<RootTaskInfo> getAllRootTaskInfosOnDisplay(int displayId) { + try { + return mAtmService.getAllRootTaskInfosOnDisplay(displayId); + } catch (RemoteException e) { + Log.e(TAG, String.format("Error while querying for tasks on display %d", displayId), e); + return Collections.emptyList(); + } + } + + private boolean shouldIncludeTask(RootTaskInfo taskInfo, Set<Integer> taskIdsToIgnore) { + // Only consider tasks that shouldn't be ignored, are visible, running, and have a launcher + // icon. Furthermore, types such as launcher/home/dock/assistant are ignored. + return !taskIdsToIgnore.contains(taskInfo.taskId) + && taskInfo.isVisible + && taskInfo.isRunning + && taskInfo.numActivities > 0 + && taskInfo.topActivity != null + && taskInfo.topActivityInfo != null + && taskInfo.childTaskIds.length > 0 + && taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_STANDARD + && canAppStartThroughLauncher(taskInfo.topActivity.getPackageName()); + } + + private boolean canAppStartThroughLauncher(String packageName) { + return getMainLauncherIntentForPackage(packageName).resolveActivity(mPackageManager) + != null; + } + + private ListenableFuture<ClipData> getBacklinksDataForTaskId(RootTaskInfo taskInfo) { + SettableFuture<ClipData> backlinksData = SettableFuture.create(); + int taskId = taskInfo.taskId; + mAssistContentRequester.requestAssistContent(taskId, assistContent -> + backlinksData.set(getBacklinksDataFromAssistContent(taskInfo, assistContent))); + return withTimeout(backlinksData, 5L, TimeUnit.SECONDS, newSingleThreadScheduledExecutor()); + } + + /** + * A utility method to get {@link ClipData} to use for Backlinks functionality from + * {@link AssistContent} received from the app whose screenshot is taken. + * + * <p>There are multiple ways an app can provide deep-linkable data via {@link AssistContent} + * but Backlinks restricts to using only one way. The following is the ordered list based on + * preference: + * <ul> + * <li>{@link AssistContent#getWebUri()} is the most preferred way. + * <li>Second preference is given to {@link AssistContent#getIntent()} when the app provides + * the intent, see {@link AssistContent#isAppProvidedIntent()}. + * <li>The last preference is given to an {@link Intent} that is built using + * {@link Intent#ACTION_MAIN} and {@link Intent#CATEGORY_LAUNCHER}. + * </ul> + * + * @param taskInfo {@link RootTaskInfo} of the task which provided the {@link AssistContent}. + * @param content the {@link AssistContent} to map into Backlinks {@link ClipData}. + * @return {@link ClipData} that represents the Backlinks data. + */ + private ClipData getBacklinksDataFromAssistContent(RootTaskInfo taskInfo, + @Nullable AssistContent content) { + String appName = getAppNameOfTask(taskInfo); + String packageName = taskInfo.topActivity.getPackageName(); + ClipData fallback = ClipData.newIntent(appName, + getMainLauncherIntentForPackage(packageName)); + if (content == null) { + return fallback; + } + + // First preference is given to app provided uri. + if (content.isAppProvidedWebUri()) { + Uri uri = content.getWebUri(); + Intent backlinksIntent = new Intent(ACTION_VIEW).setData(uri); + if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) { + return ClipData.newRawUri(appName, uri); + } + } + + // Second preference is given to app provided, hopefully deep-linking, intent. + if (content.isAppProvidedIntent()) { + Intent backlinksIntent = content.getIntent(); + if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) { + return ClipData.newIntent(appName, backlinksIntent); + } + } + + return fallback; + } + + private boolean doesIntentResolveToSamePackage(Intent intentToResolve, + String requiredPackageName) { + ComponentName resolvedComponent = intentToResolve.resolveActivity(mPackageManager); + if (resolvedComponent == null) { + return false; + } + + return resolvedComponent.getPackageName().equals(requiredPackageName); + } + + private String getAppNameOfTask(RootTaskInfo taskInfo) { + return taskInfo.topActivityInfo.loadLabel(mPackageManager).toString(); + } + + private Intent getMainLauncherIntentForPackage(String packageName) { + return new Intent(ACTION_MAIN) + .addCategory(CATEGORY_LAUNCHER) + .setPackage(packageName); + } + /** Helper factory to help with injecting {@link AppClipsViewModel}. */ static final class Factory implements ViewModelProvider.Factory { private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper; private final ImageExporter mImageExporter; + private final IActivityTaskManager mAtmService; + private final AssistContentRequester mAssistContentRequester; + private final PackageManager mPackageManager; @Main private final Executor mMainExecutor; @Background private final Executor mBgExecutor; @Inject - Factory(AppClipsCrossProcessHelper appClipsCrossProcessHelper, ImageExporter imageExporter, - @Main Executor mainExecutor, @Background Executor bgExecutor) { + Factory(AppClipsCrossProcessHelper appClipsCrossProcessHelper, ImageExporter imageExporter, + IActivityTaskManager atmService, AssistContentRequester assistContentRequester, + PackageManager packageManager, @Main Executor mainExecutor, + @Background Executor bgExecutor) { mAppClipsCrossProcessHelper = appClipsCrossProcessHelper; mImageExporter = imageExporter; + mAtmService = atmService; + mAssistContentRequester = assistContentRequester; + mPackageManager = packageManager; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; } @@ -176,7 +377,8 @@ final class AppClipsViewModel extends ViewModel { //noinspection unchecked return (T) new AppClipsViewModel(mAppClipsCrossProcessHelper, mImageExporter, - mMainExecutor, mBgExecutor); + mAtmService, mAssistContentRequester, mPackageManager, mMainExecutor, + mBgExecutor); } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt index 442b3873be4d..0fefa0b7757a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt @@ -128,8 +128,9 @@ constructor(private val buttonViewBinder: ActionButtonViewBinder) { } } launch { - viewModel.previewAction.collect { onClick -> - previewView.setOnClickListener { onClick?.invoke() } + viewModel.previewAction.collect { action -> + previewView.setOnClickListener { action?.onClick?.invoke() } + previewView.contentDescription = action?.contentDescription } } launch { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt index 3f99bc4597cb..25420d44dc79 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt @@ -31,8 +31,8 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager val scrollingScrim: StateFlow<Bitmap?> = _scrollingScrim private val _badge = MutableStateFlow<Drawable?>(null) val badge: StateFlow<Drawable?> = _badge - private val _previewAction = MutableStateFlow<(() -> Unit)?>(null) - val previewAction: StateFlow<(() -> Unit)?> = _previewAction + private val _previewAction = MutableStateFlow<PreviewAction?>(null) + val previewAction: StateFlow<PreviewAction?> = _previewAction private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>()) val actions: StateFlow<List<ActionButtonViewModel>> = _actions private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED) @@ -57,8 +57,8 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager _badge.value = badge } - fun setPreviewAction(onClick: () -> Unit) { - _previewAction.value = onClick + fun setPreviewAction(previewAction: PreviewAction) { + _previewAction.value = previewAction } fun addAction( @@ -149,6 +149,11 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager } } +data class PreviewAction( + val contentDescription: CharSequence, + val onClick: () -> Unit, +) + enum class AnimationState { NOT_STARTED, ENTRANCE_STARTED, // The first 200ms of the entrance animation diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 262befc1e9ad..1d43ec277222 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -25,7 +25,6 @@ import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.Flags.predictiveBackAnimateShade; -import static com.android.systemui.Flags.shadeCollapseActivityLaunchFix; import static com.android.systemui.Flags.smartspaceRelocateToBottom; import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; import static com.android.systemui.classifier.Classifier.GENERIC; @@ -484,7 +483,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private float mBottomAreaShadeAlpha; final ValueAnimator mBottomAreaShadeAlphaAnimator; private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha", - NotificationPanelView::setPanelAlphaInternal, + (view, alpha) -> { + setAlphaInternal(alpha); + }, NotificationPanelView::getCurrentPanelAlpha, R.id.panel_alpha_animator_tag, R.id.panel_alpha_animator_start_tag, R.id.panel_alpha_animator_end_tag); @@ -1043,6 +1044,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mView.setTranslationY(0f); }) .start(); + } else { + mView.postDelayed(() -> { + instantCollapse(); + }, unlockAnimationStartDelay); } } } @@ -3075,6 +3080,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } + private void setAlphaInternal(float alpha) { + mKeyguardInteractor.setPanelAlpha(alpha / 255f); + mView.setPanelAlphaInternal(alpha); + } + @Override public void setAlphaChangeAnimationEndAction(Runnable r) { mPanelAlphaEndAction = r; @@ -3384,7 +3394,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Updates the views to the initial state for the fold to AOD animation. */ @Override public void prepareFoldToAodAnimation() { - if (!MigrateClocksToBlueprint.isEnabled()) { + if (MigrateClocksToBlueprint.isEnabled()) { + setDozing(true /* dozing */, false /* animate */); + } else { // Force show AOD UI even if we are not locked showAodUi(); } @@ -4118,11 +4130,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public boolean canBeCollapsed() { - return !isFullyCollapsed() && !isTracking() && !isClosing() - // Don't try to collapse if on keyguard, as the expansion fraction is 1 in this - // case. - && !(shadeCollapseActivityLaunchFix() && mExpandedFraction == 1f - && mBarState == KEYGUARD); + return !isFullyCollapsed() && !isTracking() && !isClosing(); } public void instantCollapse() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index 004db16b7e92..ce321dcc03fd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.view.MotionEvent +import androidx.compose.ui.Alignment import com.android.systemui.assist.AssistManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -24,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse import com.android.systemui.shade.ShadeController.ShadeVisibilityListener import com.android.systemui.shade.domain.interactor.ShadeInteractor @@ -175,11 +177,16 @@ constructor( sceneInteractor.changeScene( SceneFamilies.NotifShade, "ShadeController.animateExpandShade", + OpenBottomShade.takeIf { shadeInteractor.shadeAlignment == Alignment.BottomEnd } ) } override fun expandToQs() { - sceneInteractor.changeScene(SceneFamilies.QuickSettings, "ShadeController.animateExpandQs") + sceneInteractor.changeScene( + SceneFamilies.QuickSettings, + "ShadeController.animateExpandQs", + OpenBottomShade.takeIf { shadeInteractor.shadeAlignment == Alignment.BottomEnd } + ) } override fun setVisibilityListener(listener: ShadeVisibilityListener) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index 7e1a3109fcbb..4d43ad52019a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -15,7 +15,10 @@ */ package com.android.systemui.shade.data.repository +import android.content.Context import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject @@ -104,6 +107,9 @@ interface ShadeRepository { val shadeMode: StateFlow<ShadeMode> + /** Whether dual shade should be aligned to the bottom (true) or to the top (false). */ + val isDualShadeAlignedToBottom: Boolean + /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */ @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean> @@ -174,7 +180,8 @@ interface ShadeRepository { /** Business logic for shade interactions */ @SysUISingleton -class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { +class ShadeRepositoryImpl @Inject constructor(@Application applicationContext: Context) : + ShadeRepository { private val _qsExpansion = MutableStateFlow(0f) override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow() @@ -223,6 +230,9 @@ class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { val _shadeMode = MutableStateFlow(if (DualShade.isEnabled) ShadeMode.Dual else ShadeMode.Single) override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow() + override val isDualShadeAlignedToBottom = + applicationContext.resources.getBoolean(R.bool.config_dualShadeAlignedToBottom) + override fun setShadeMode(shadeMode: ShadeMode) { _shadeMode.value = shadeMode } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 18407cc8e76f..ef0a842ecc0c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.domain.interactor +import com.android.systemui.shade.shared.model.ShadeAlignment import com.android.systemui.shade.shared.model.ShadeMode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -53,6 +54,9 @@ interface ShadeInteractor : BaseShadeInteractor { /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ val isExpandToQsEnabled: Flow<Boolean> + + /** How to align the shade content. */ + val shadeAlignment: ShadeAlignment } /** ShadeInteractor methods with implementations that differ between non-empty impls. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt index bb4baa34dd86..6226d077b135 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.shared.model.ShadeAlignment import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -46,4 +47,5 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor { override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single) + override val shadeAlignment: ShadeAlignment = ShadeAlignment.Top } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt index 4014512d4e19..55f019b01164 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -24,6 +24,8 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.shared.model.ShadeAlignment import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository @@ -51,6 +53,7 @@ constructor( keyguardRepository: KeyguardRepository, keyguardTransitionInteractor: KeyguardTransitionInteractor, powerInteractor: PowerInteractor, + shadeRepository: ShadeRepository, userSetupRepository: UserSetupRepository, userSwitcherInteractor: UserSwitcherInteractor, private val baseShadeInteractor: BaseShadeInteractor, @@ -99,6 +102,13 @@ constructor( } } + override val shadeAlignment: ShadeAlignment = + if (shadeRepository.isDualShadeAlignedToBottom) { + ShadeAlignment.Bottom + } else { + ShadeAlignment.Top + } + override val isExpandToQsEnabled: Flow<Boolean> = combine( disableFlagsRepository.disableFlags, diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeAlignment.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeAlignment.kt new file mode 100644 index 000000000000..0690537944e2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeAlignment.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.shade.shared.model + +/** Enumerates all supported alignments of the shade. */ +sealed interface ShadeAlignment { + + /** Aligns the shade to the top. */ + data object Top : ShadeAlignment + + /** Aligns the shade to the bottom. */ + data object Bottom : ShadeAlignment +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt index 0314091e792e..6551854dcb36 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.domain.interactor.ShadeInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -38,6 +39,7 @@ class OverlayShadeViewModel constructor( @Application applicationScope: CoroutineScope, private val sceneInteractor: SceneInteractor, + shadeInteractor: ShadeInteractor ) { /** The scene to show in the background when the overlay shade is open. */ val backgroundScene: StateFlow<SceneKey> = @@ -49,6 +51,9 @@ constructor( initialValue = Scenes.Lockscreen, ) + /** Dictates the alignment of the overlay shade panel on the screen. */ + val panelAlignment = shadeInteractor.shadeAlignment + /** Notifies that the user has clicked the semi-transparent background scrim. */ fun onScrimClicked() { sceneInteractor.changeScene( diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt index 6d951bf4d83b..abffd3c53908 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt @@ -18,8 +18,10 @@ package com.android.systemui.smartspace.dagger import android.app.ActivityOptions import android.app.PendingIntent import android.content.Intent +import android.os.Handler import android.view.View import android.view.ViewGroup +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM @@ -51,19 +53,20 @@ interface SmartspaceViewComponent { @Provides fun providesSmartspaceView( - activityStarter: ActivityStarter, - falsingManager: FalsingManager, - parent: ViewGroup, - @Named(PLUGIN) plugin: BcSmartspaceDataPlugin, - viewWithCustomLayout: View?, - onAttachListener: View.OnAttachStateChangeListener - ): - BcSmartspaceDataPlugin.SmartspaceView { + activityStarter: ActivityStarter, + falsingManager: FalsingManager, + parent: ViewGroup, + @Named(PLUGIN) plugin: BcSmartspaceDataPlugin, + viewWithCustomLayout: View?, + onAttachListener: View.OnAttachStateChangeListener, + @Background bgHandler: Handler, + ): BcSmartspaceDataPlugin.SmartspaceView { val ssView = viewWithCustomLayout as? BcSmartspaceDataPlugin.SmartspaceView ?: plugin.getView(parent) // Currently, this is only used to provide SmartspaceView on Dream surface. ssView.setUiSurface(UI_SURFACE_DREAM) + ssView.setBgHandler(bgHandler) ssView.registerDataProvider(plugin) ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 95cabfb53e72..2f3fc729464e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -386,6 +386,11 @@ public class KeyguardIndicationController { mStatusBarStateListener.onDozingChanged(mStatusBarStateController.isDozing()); } + @Nullable + public ViewGroup getIndicationArea() { + return mIndicationArea; + } + public void setIndicationArea(ViewGroup indicationArea) { mIndicationArea = indicationArea; mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text); @@ -1665,11 +1670,6 @@ public class KeyguardIndicationController { private final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override - public void onStateChanged(int newState) { - setVisible(newState == StatusBarState.KEYGUARD); - } - - @Override public void onDozingChanged(boolean dozing) { if (mDozing == dozing) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 04a413acdf0b..240953d6bfcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -291,8 +291,9 @@ public class NotificationMediaManager implements Dumpable { } private void updateMediaMetaData(MediaListener callback) { - callback.onPrimaryMetadataOrStateChanged(mMediaMetadata, - getMediaControllerPlaybackState(mMediaController)); + int playbackState = getMediaControllerPlaybackState(mMediaController); + mHandler.post( + () -> callback.onPrimaryMetadataOrStateChanged(mMediaMetadata, playbackState)); } public void removeCallback(MediaListener callback) { @@ -437,9 +438,11 @@ public class NotificationMediaManager implements Dumpable { private void updateMediaMetaData(List<MediaListener> callbacks) { @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController); - for (int i = 0; i < callbacks.size(); i++) { - callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state); - } + mHandler.post(() -> { + for (int i = 0; i < callbacks.size(); i++) { + callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state); + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 9a82ecf01449..28e3a8355f45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -40,6 +40,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -261,14 +262,17 @@ public class NotificationShelf extends ActivatableNotificationView { viewState.hasItemsInStableShelf = false; } - final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); + final float stackBottom = SceneContainerFlag.isEnabled() + ? ambientState.getStackTop() + ambientState.getStackHeight() + : ambientState.getStackY() + ambientState.getStackHeight(); + if (viewState.hidden) { // if the shelf is hidden, position it at the end of the stack (plus the clip // padding), such that when it appears animated, it will smoothly move in from the // bottom, without jump cutting any notifications - viewState.setYTranslation(stackEnd + mPaddingBetweenElements); + viewState.setYTranslation(stackBottom + mPaddingBetweenElements); } else { - viewState.setYTranslation(stackEnd - viewState.height); + viewState.setYTranslation(stackBottom - viewState.height); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index d0702fcc0c5e..bbf0ae1700c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -69,7 +69,6 @@ import com.android.systemui.util.drawable.DrawableSize; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; @@ -148,11 +147,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @VisibleForTesting float mScaleToFitNewIconSize = 1; private StatusBarIcon mIcon; @ViewDebug.ExportedProperty private String mSlot; - private Drawable mNumberBackground; - private Paint mNumberPain; - private int mNumberX; - private int mNumberY; - private String mNumberText; private StatusBarNotification mNotification; private final boolean mBlocked; private Configuration mConfiguration; @@ -201,10 +195,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi mDozer = new NotificationDozeHelper(); mBlocked = blocked; mSlot = slot; - mNumberPain = new Paint(); - mNumberPain.setTextAlign(Paint.Align.CENTER); - mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color)); - mNumberPain.setAntiAlias(true); setNotification(sbn); setScaleType(ScaleType.CENTER); mConfiguration = new Configuration(context.getResources().getConfiguration()); @@ -410,8 +400,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi && mIcon.iconLevel == icon.iconLevel; final boolean visibilityEquals = mIcon != null && mIcon.visible == icon.visible; - final boolean numberEquals = mIcon != null - && mIcon.number == icon.number; mIcon = icon.clone(); setContentDescription(icon.contentDescription); if (!iconEquals) { @@ -425,20 +413,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi setImageLevel(icon.iconLevel); } - if (!numberEquals) { - if (icon.number > 0 && getContext().getResources().getBoolean( - R.bool.config_statusBarShowNumber)) { - if (mNumberBackground == null) { - mNumberBackground = getContext().getResources().getDrawable( - R.drawable.ic_notification_overlay); - } - placeNumber(); - } else { - mNumberBackground = null; - mNumberText = null; - } - invalidate(); - } if (!visibilityEquals) { setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE); } @@ -568,14 +542,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (mNumberBackground != null) { - placeNumber(); - } - } - - @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); updateDrawable(); @@ -609,10 +575,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi canvas.restore(); } - if (mNumberBackground != null) { - mNumberBackground.draw(canvas); - canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain); - } if (mDotAppearAmount != 0.0f) { float radius; float alpha = Color.alpha(mDecorColor) / 255.f; @@ -640,39 +602,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi Log.d("View", debugIndent(depth) + "icon=" + mIcon); } - void placeNumber() { - final String str; - final int tooBig = getContext().getResources().getInteger( - android.R.integer.status_bar_notification_info_maxnum); - if (mIcon.number > tooBig) { - str = getContext().getResources().getString( - android.R.string.status_bar_notification_info_overflow); - } else { - NumberFormat f = NumberFormat.getIntegerInstance(); - str = f.format(mIcon.number); - } - mNumberText = str; - - final int w = getWidth(); - final int h = getHeight(); - final Rect r = new Rect(); - mNumberPain.getTextBounds(str, 0, str.length(), r); - final int tw = r.right - r.left; - final int th = r.bottom - r.top; - mNumberBackground.getPadding(r); - int dw = r.left + tw + r.right; - if (dw < mNumberBackground.getMinimumWidth()) { - dw = mNumberBackground.getMinimumWidth(); - } - mNumberX = w-r.right-((dw-r.right-r.left)/2); - int dh = r.top + th + r.bottom; - if (dh < mNumberBackground.getMinimumWidth()) { - dh = mNumberBackground.getMinimumWidth(); - } - mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2); - mNumberBackground.setBounds(w-dw, h-dh, w, h); - } - @Override public String toString() { return "StatusBarIconView(" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index ad09aa3ca3a0..79f1874c1f32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.call.ui.viewmodel +import android.view.View import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.shared.model.Icon @@ -24,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor +import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel @@ -59,33 +61,38 @@ constructor( val startTimeInElapsedRealtime = state.startTimeMs - systemClock.currentTimeMillis() + systemClock.elapsedRealtime() - OngoingActivityChipModel.Shown( + OngoingActivityChipModel.Shown.Timer( icon = Icon.Resource( com.android.internal.R.drawable.ic_phone, contentDescription = null, ), + colors = ColorsModel.Themed, startTimeMs = startTimeInElapsedRealtime, - ) { - if (state.intent != null) { - val backgroundView = - it.requireViewById<ChipBackgroundContainer>( - R.id.ongoing_activity_chip_background - ) - // TODO(b/332662551): Log the click event. - // This mimics OngoingCallController#updateChipClickListener. - activityStarter.postStartActivityDismissingKeyguard( - state.intent, - ActivityTransitionAnimator.Controller.fromView( - backgroundView, - InteractionJankMonitor - .CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, - ) - ) - } - } + getOnClickListener(state), + ) } } } .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden) + + private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? { + if (state.intent == null) { + return null + } + + return View.OnClickListener { view -> + val backgroundView = + view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background) + // TODO(b/332662551): Log the click event. + // This mimics OngoingCallController#updateChipClickListener. + activityStarter.postStartActivityDismissingKeyguard( + state.intent, + ActivityTransitionAnimator.Controller.fromView( + backgroundView, + InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + ) + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt index a1678bf3d452..42e921ec2b69 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastToO import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper +import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener @@ -69,7 +70,8 @@ constructor( } } } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden) + // See b/347726238. + .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden) /** Stops the currently active projection. */ private fun stopProjecting() { @@ -79,12 +81,13 @@ constructor( private fun createCastToOtherDeviceChip( state: ProjectionChipModel.Projecting, ): OngoingActivityChipModel.Shown { - return OngoingActivityChipModel.Shown( + return OngoingActivityChipModel.Shown.Timer( icon = Icon.Resource( CAST_TO_OTHER_DEVICE_ICON, ContentDescription.Resource(R.string.accessibility_casting), ), + colors = ColorsModel.Red, // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), createDialogLaunchOnClickListener( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt index 1e9f0a144d80..43b1d1681a4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt @@ -50,7 +50,6 @@ constructor( ) { screenRecordState, mediaProjectionState -> when (screenRecordState) { is ScreenRecordModel.DoingNothing -> ScreenRecordChipModel.DoingNothing - // TODO(b/332662551): Implement the 3-2-1 countdown chip. is ScreenRecordModel.Starting -> ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted) is ScreenRecordModel.Recording -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt index d190cfdd1175..af6d7f27eddd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt @@ -23,10 +23,12 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R +import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate +import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener @@ -54,12 +56,17 @@ constructor( .map { state -> when (state) { is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden - // TODO(b/332662551): Implement the 3-2-1 countdown chip. - is ScreenRecordChipModel.Starting -> OngoingActivityChipModel.Hidden + is ScreenRecordChipModel.Starting -> { + OngoingActivityChipModel.Shown.Countdown( + colors = ColorsModel.Red, + secondsUntilStarted = state.millisUntilStarted.toCountdownSeconds(), + ) + } is ScreenRecordChipModel.Recording -> { - OngoingActivityChipModel.Shown( + OngoingActivityChipModel.Shown.Timer( // TODO(b/332662551): Also provide a content description. icon = Icon.Resource(ICON, contentDescription = null), + colors = ColorsModel.Red, startTimeMs = systemClock.elapsedRealtime(), createDialogLaunchOnClickListener( createDelegate(state.recordedTask), @@ -69,7 +76,8 @@ constructor( } } } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden) + // See b/347726238. + .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden) private fun createDelegate( recordedTask: ActivityManager.RunningTaskInfo? diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt index dc4100269702..c3b145624aba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate +import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener @@ -65,7 +66,8 @@ constructor( } } } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden) + // See b/347726238. + .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden) /** Stops the currently active projection. */ private fun stopProjecting() { @@ -75,9 +77,10 @@ constructor( private fun createShareToAppChip( state: ProjectionChipModel.Projecting, ): OngoingActivityChipModel.Shown { - return OngoingActivityChipModel.Shown( + return OngoingActivityChipModel.Shown.Timer( // TODO(b/332662551): Use the right content description. icon = Icon.Resource(SHARE_TO_APP_ICON, contentDescription = null), + colors = ColorsModel.Red, // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), createDialogLaunchOnClickListener( @@ -95,7 +98,6 @@ constructor( ) companion object { - // TODO(b/332662551): Use the right icon. - @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_screenshot_share + @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt new file mode 100644 index 000000000000..b2140f793ced --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.chips.ui.model + +import android.content.Context +import android.content.res.ColorStateList +import androidx.annotation.ColorInt +import com.android.settingslib.Utils +import com.android.systemui.res.R + +/** Model representing how the chip in the status bar should be colored. */ +sealed interface ColorsModel { + /** The color for the background of the chip. */ + fun background(context: Context): ColorStateList + + /** The color for the text (and icon) on the chip. */ + @ColorInt fun text(context: Context): Int + + /** The chip should match the theme's primary color. */ + data object Themed : ColorsModel { + override fun background(context: Context): ColorStateList = + Utils.getColorAttr(context, com.android.internal.R.attr.colorAccent) + + override fun text(context: Context) = + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary) + } + + /** The chip should have a red background with white text. */ + data object Red : ColorsModel { + override fun background(context: Context): ColorStateList = + ColorStateList.valueOf(context.getColor(R.color.GM2_red_600)) + + override fun text(context: Context) = context.getColor(android.R.color.white) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index e63713bf1602..57f609b4b534 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -25,20 +25,42 @@ sealed class OngoingActivityChipModel { data object Hidden : OngoingActivityChipModel() /** This chip should be shown with the given information. */ - data class Shown( - /** The icon to show on the chip. */ - val icon: Icon, + abstract class Shown( + /** The icon to show on the chip. If null, no icon will be shown. */ + open val icon: Icon?, + /** What colors to use for the chip. */ + open val colors: ColorsModel, /** - * The time this event started, used to show the timer. - * - * This time should be relative to - * [com.android.systemui.util.time.SystemClock.elapsedRealtime], *not* - * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because the - * [ChipChronometer] is based off of elapsed realtime. See - * [android.widget.Chronometer.setBase]. + * Listener method to invoke when this chip is clicked. If null, the chip won't be + * clickable. */ - val startTimeMs: Long, - /** Listener method to invoke when this chip is clicked. */ - val onClickListener: View.OnClickListener, - ) : OngoingActivityChipModel() + open val onClickListener: View.OnClickListener?, + ) : OngoingActivityChipModel() { + /** The chip shows a timer, counting up from [startTimeMs]. */ + data class Timer( + override val icon: Icon, + override val colors: ColorsModel, + /** + * The time this event started, used to show the timer. + * + * This time should be relative to + * [com.android.systemui.util.time.SystemClock.elapsedRealtime], *not* + * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because the + * [ChipChronometer] is based off of elapsed realtime. See + * [android.widget.Chronometer.setBase]. + */ + val startTimeMs: Long, + override val onClickListener: View.OnClickListener?, + ) : Shown(icon, colors, onClickListener) + + /** + * This chip shows a countdown using [secondsUntilStarted]. Used to inform users that an + * event is about to start. Typically, a [Countdown] chip will turn into a [Timer] chip. + */ + data class Countdown( + override val colors: ColorsModel, + /** The number of seconds until an event is started. */ + val secondsUntilStarted: Long, + ) : Shown(icon = null, colors, onClickListener = null) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt index 1b79ce469960..9c8086fd073d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt @@ -72,5 +72,8 @@ constructor( else -> call } } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden) + // Some of the chips could have timers in them and we don't want the start time + // for those timers to get reset for any reason. So, as soon as any subscriber has + // requested the chip information, we need to maintain it forever. See b/347726238. + .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index ee2c9cc9acde..af8a89d82fe7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -109,6 +109,7 @@ constructor( @Main private val uiExecutor: Executor, @Background private val bgExecutor: Executor, @Main private val handler: Handler, + @Background private val bgHandler: Handler, @Named(DATE_SMARTSPACE_DATA_PLUGIN) optionalDatePlugin: Optional<BcSmartspaceDataPlugin>, @Named(WEATHER_SMARTSPACE_DATA_PLUGIN) @@ -412,6 +413,7 @@ constructor( val ssView = plugin.getView(parent) configPlugin?.let { ssView.registerConfigProvider(it) } + ssView.setBgHandler(bgHandler) ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) ssView.setTimeChangedDelegate(SmartspaceTimeChangedDelegate(keyguardUpdateMonitor)) ssView.registerDataProvider(plugin) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 4c66f6617312..0bb18d704ac7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -58,7 +58,7 @@ class NotificationWakeUpCoordinator constructor( @Application applicationScope: CoroutineScope, dumpManager: DumpManager, - private val mHeadsUpManager: HeadsUpManager, + private val headsUpManager: HeadsUpManager, private val statusBarStateController: StatusBarStateController, private val bypassController: KeyguardBypassController, private val dozeParameters: DozeParameters, @@ -71,8 +71,8 @@ constructor( StatusBarStateController.StateListener, ShadeExpansionListener, Dumpable { - private lateinit var mStackScrollerController: NotificationStackScrollLayoutController - private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE + private lateinit var stackScrollerController: NotificationStackScrollLayoutController + private var visibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE private var inputLinearDozeAmount: Float = 0.0f private var inputEasedDozeAmount: Float = 0.0f @@ -85,13 +85,13 @@ constructor( private var outputEasedDozeAmount: Float = 0.0f @VisibleForTesting val dozeAmountInterpolator: Interpolator = Interpolators.FAST_OUT_SLOW_IN - private var mNotificationVisibleAmount = 0.0f - private var mNotificationsVisible = false - private var mNotificationsVisibleForExpansion = false - private var mVisibilityAnimator: ObjectAnimator? = null - private var mVisibilityAmount = 0.0f - private var mLinearVisibilityAmount = 0.0f - private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>() + private var notificationVisibleAmount = 0.0f + private var notificationsVisible = false + private var notificationsVisibleForExpansion = false + private var visibilityAnimator: ObjectAnimator? = null + private var visibilityAmount = 0.0f + private var linearVisibilityAmount = 0.0f + private val entrySetToClearWhenFinished = mutableSetOf<NotificationEntry>() private var pulseExpanding: Boolean = false private val wakeUpListeners = arrayListOf<WakeUpListener>() private var state: Int = StatusBarState.KEYGUARD @@ -104,14 +104,14 @@ constructor( willWakeUp = false if (value) { if ( - mNotificationsVisible && - !mNotificationsVisibleForExpansion && + notificationsVisible && + !notificationsVisibleForExpansion && !bypassController.bypassEnabled ) { // We're waking up while pulsing, let's make sure the animation looks nice - mStackScrollerController.wakeUpFromPulse() + stackScrollerController.wakeUpFromPulse() } - if (bypassController.bypassEnabled && !mNotificationsVisible) { + if (bypassController.bypassEnabled && !notificationsVisible) { // Let's make sure our huns become visible once we are waking up in case // they were blocked by the proximity sensor updateNotificationVisibility( @@ -186,13 +186,13 @@ constructor( init { dumpManager.registerDumpable(this) - mHeadsUpManager.addListener(this) + headsUpManager.addListener(this) statusBarStateController.addCallback(this) bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener) addListener( object : WakeUpListener { override fun onFullyHiddenChanged(isFullyHidden: Boolean) { - if (isFullyHidden && mNotificationsVisibleForExpansion) { + if (isFullyHidden && notificationsVisibleForExpansion) { // When the notification becomes fully invisible, let's make sure our // expansion // flag also changes. This can happen if the bouncer shows when dragging @@ -217,7 +217,7 @@ constructor( } fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) { - mStackScrollerController = stackScrollerController + this.stackScrollerController = stackScrollerController pulseExpanding = stackScrollerController.isPulseExpanding stackScrollerController.setOnPulseHeightChangedListener { val nowExpanding = isPulseExpanding() @@ -237,7 +237,7 @@ constructor( } } - fun isPulseExpanding(): Boolean = mStackScrollerController.isPulseExpanding + fun isPulseExpanding(): Boolean = stackScrollerController.isPulseExpanding /** * @param visible should notifications be visible @@ -249,13 +249,13 @@ constructor( animate: Boolean, increaseSpeed: Boolean ) { - mNotificationsVisibleForExpansion = visible + notificationsVisibleForExpansion = visible updateNotificationVisibility(animate, increaseSpeed) - if (!visible && mNotificationsVisible) { + if (!visible && notificationsVisible) { // If we stopped expanding and we're still visible because we had a pulse that hasn't // times out, let's release them all to make sure were not stuck in a state where // notifications are visible - mHeadsUpManager.releaseAllImmediately() + headsUpManager.releaseAllImmediately() } } @@ -269,12 +269,12 @@ constructor( private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) { // TODO: handle Lockscreen wakeup for bypass when we're not pulsing anymore - var visible = mNotificationsVisibleForExpansion || mHeadsUpManager.hasNotifications() + var visible = notificationsVisibleForExpansion || headsUpManager.hasNotifications() visible = visible && canShowPulsingHuns if ( !visible && - mNotificationsVisible && + notificationsVisible && (wakingUp || willWakeUp) && outputLinearDozeAmount != 0.0f ) { @@ -290,11 +290,11 @@ constructor( animate: Boolean, increaseSpeed: Boolean ) { - if (mNotificationsVisible == visible) { + if (notificationsVisible == visible) { return } - mNotificationsVisible = visible - mVisibilityAnimator?.cancel() + notificationsVisible = visible + visibilityAnimator?.cancel() if (animate) { notifyAnimationStart(visible) startVisibilityAnimation(increaseSpeed) @@ -371,7 +371,7 @@ constructor( state = statusBarStateController.state, changed = changed ) - mStackScrollerController.setDozeAmount(outputEasedDozeAmount) + stackScrollerController.setDozeAmount(outputEasedDozeAmount) updateHideAmount() if (changed && outputLinearDozeAmount == 0.0f) { setNotificationsVisible(visible = false, animate = false, increaseSpeed = false) @@ -475,7 +475,7 @@ constructor( this.collapsedEnoughToHide = collapsedEnough if (couldShowPulsingHuns && !canShowPulsingHuns) { updateNotificationVisibility(animate = true, increaseSpeed = true) - mHeadsUpManager.releaseAllImmediately() + headsUpManager.releaseAllImmediately() } } } @@ -562,12 +562,12 @@ constructor( } private fun startVisibilityAnimation(increaseSpeed: Boolean) { - if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) { - mVisibilityInterpolator = - if (mNotificationsVisible) Interpolators.TOUCH_RESPONSE + if (notificationVisibleAmount == 0f || notificationVisibleAmount == 1f) { + visibilityInterpolator = + if (notificationsVisible) Interpolators.TOUCH_RESPONSE else Interpolators.FAST_OUT_SLOW_IN_REVERSE } - val target = if (mNotificationsVisible) 1.0f else 0.0f + val target = if (notificationsVisible) 1.0f else 0.0f val visibilityAnimator = ObjectAnimator.ofFloat(this, notificationVisibility, target) visibilityAnimator.interpolator = InterpolatorsAndroidX.LINEAR var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong() @@ -576,34 +576,34 @@ constructor( } visibilityAnimator.duration = duration visibilityAnimator.start() - mVisibilityAnimator = visibilityAnimator + this.visibilityAnimator = visibilityAnimator } private fun setVisibilityAmount(visibilityAmount: Float) { logger.logSetVisibilityAmount(visibilityAmount) - mLinearVisibilityAmount = visibilityAmount - mVisibilityAmount = mVisibilityInterpolator.getInterpolation(visibilityAmount) + linearVisibilityAmount = visibilityAmount + this.visibilityAmount = visibilityInterpolator.getInterpolation(visibilityAmount) handleAnimationFinished() updateHideAmount() } private fun handleAnimationFinished() { - if (outputLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) { - mEntrySetToClearWhenFinished.forEach { it.setHeadsUpAnimatingAway(false) } - mEntrySetToClearWhenFinished.clear() + if (outputLinearDozeAmount == 0.0f || linearVisibilityAmount == 0.0f) { + entrySetToClearWhenFinished.forEach { it.setHeadsUpAnimatingAway(false) } + entrySetToClearWhenFinished.clear() } } private fun updateHideAmount() { - val linearAmount = min(1.0f - mLinearVisibilityAmount, outputLinearDozeAmount) - val amount = min(1.0f - mVisibilityAmount, outputEasedDozeAmount) + val linearAmount = min(1.0f - linearVisibilityAmount, outputLinearDozeAmount) + val amount = min(1.0f - visibilityAmount, outputEasedDozeAmount) logger.logSetHideAmount(linearAmount) - mStackScrollerController.setHideAmount(linearAmount, amount) + stackScrollerController.setHideAmount(linearAmount, amount) notificationsFullyHidden = linearAmount == 1.0f } private fun notifyAnimationStart(awake: Boolean) { - mStackScrollerController.notifyHideAnimationStart(!awake) + stackScrollerController.notifyHideAnimationStart(!awake) } override fun onDozingChanged(isDozing: Boolean) { @@ -615,7 +615,7 @@ constructor( override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { var animate = shouldAnimateVisibility() if (!isHeadsUp) { - if (outputLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) { + if (outputLinearDozeAmount != 0.0f && linearVisibilityAmount != 0.0f) { if (entry.isRowDismissed) { // if we animate, we see the shelf briefly visible. Instead we fully animate // the notification and its background out @@ -623,11 +623,11 @@ constructor( } else if (!wakingUp && !willWakeUp) { // TODO: look that this is done properly and not by anyone else entry.setHeadsUpAnimatingAway(true) - mEntrySetToClearWhenFinished.add(entry) + entrySetToClearWhenFinished.add(entry) } } - } else if (mEntrySetToClearWhenFinished.contains(entry)) { - mEntrySetToClearWhenFinished.remove(entry) + } else if (entrySetToClearWhenFinished.contains(entry)) { + entrySetToClearWhenFinished.remove(entry) entry.setHeadsUpAnimatingAway(false) } updateNotificationVisibility(animate, increaseSpeed = false) @@ -644,11 +644,11 @@ constructor( pw.println("hardDozeAmountOverrideSource: $hardDozeAmountOverrideSource") pw.println("outputLinearDozeAmount: $outputLinearDozeAmount") pw.println("outputEasedDozeAmount: $outputEasedDozeAmount") - pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount") - pw.println("mNotificationsVisible: $mNotificationsVisible") - pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion") - pw.println("mVisibilityAmount: $mVisibilityAmount") - pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount") + pw.println("notificationVisibleAmount: $notificationVisibleAmount") + pw.println("notificationsVisible: $notificationsVisible") + pw.println("notificationsVisibleForExpansion: $notificationsVisibleForExpansion") + pw.println("visibilityAmount: $visibilityAmount") + pw.println("linearVisibilityAmount: $linearVisibilityAmount") pw.println("pulseExpanding: $pulseExpanding") pw.println("state: ${StatusBarState.toString(state)}") pw.println("fullyAwake: $fullyAwake") @@ -698,7 +698,7 @@ constructor( } override fun get(coordinator: NotificationWakeUpCoordinator): Float { - return coordinator.mLinearVisibilityAmount + return coordinator.linearVisibilityAmount } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 1adfef061235..f98a88f5328b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -68,9 +68,11 @@ import com.android.systemui.statusbar.notification.icon.IconPack; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotificationGuts; +import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository; import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel; import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; +import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel; import com.android.systemui.statusbar.notification.stack.PriorityBucket; import com.android.systemui.util.ListenerSet; @@ -97,7 +99,7 @@ import java.util.Objects; * At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can * clean this up in the future. */ -public final class NotificationEntry extends ListEntry { +public final class NotificationEntry extends ListEntry implements NotificationRowRepository { private final String mKey; private StatusBarNotification mSbn; @@ -159,6 +161,8 @@ public final class NotificationEntry extends ListEntry { StateFlowKt.MutableStateFlow(null); private final MutableStateFlow<CharSequence> mHeadsUpStatusBarTextPublic = StateFlowKt.MutableStateFlow(null); + private final MutableStateFlow<RichOngoingContentModel> mRichOngoingContentModel = + StateFlowKt.MutableStateFlow(null); // indicates when this entry's view was first attached to a window // this value will reset when the view is completely removed from the shade (ie: filtered out) @@ -945,6 +949,7 @@ public final class NotificationEntry extends ListEntry { } /** @see #setHeadsUpStatusBarText(CharSequence) */ + @NonNull public StateFlow<CharSequence> getHeadsUpStatusBarText() { return mHeadsUpStatusBarText; } @@ -959,10 +964,17 @@ public final class NotificationEntry extends ListEntry { } /** @see #setHeadsUpStatusBarTextPublic(CharSequence) */ + @NonNull public StateFlow<CharSequence> getHeadsUpStatusBarTextPublic() { return mHeadsUpStatusBarTextPublic; } + /** Gets the current RON content model, which may be null */ + @NonNull + public StateFlow<RichOngoingContentModel> getRichOngoingContentModel() { + return mRichOngoingContentModel; + } + /** * Sets the text to be displayed on the StatusBar, when this notification is the top pinned * heads up, and its content is sensitive right now. @@ -1047,6 +1059,7 @@ public final class NotificationEntry extends ListEntry { HeadsUpStatusBarModel headsUpStatusBarModel = contentModel.getHeadsUpStatusBarModel(); this.mHeadsUpStatusBarText.setValue(headsUpStatusBarModel.getPrivateText()); this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarModel.getPublicText()); + this.mRichOngoingContentModel.setValue(contentModel.getRichOngoingContentModel()); } /** Information about a suggestion that is being edited. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt index 5adf31b75fa7..f166d32812ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt @@ -13,12 +13,18 @@ class VisualStabilityProvider @Inject constructor() { /** The subset of active listeners which are temporary (will be removed after called) */ private val temporaryListeners = ArraySet<OnReorderingAllowedListener>() + private val banListeners = ListenerSet<OnReorderingBannedListener>() + var isReorderingAllowed = true set(value) { if (field != value) { field = value if (value) { notifyReorderingAllowed() + } else { + banListeners.forEach { listener -> + listener.onReorderingBanned() + } } } } @@ -38,6 +44,10 @@ class VisualStabilityProvider @Inject constructor() { allListeners.addIfAbsent(listener) } + fun addPersistentReorderingBannedListener(listener: OnReorderingBannedListener) { + banListeners.addIfAbsent(listener) + } + /** Add a listener which will be removed when it is called. */ fun addTemporaryReorderingAllowedListener(listener: OnReorderingAllowedListener) { // Only add to the temporary set if it was added to the global set @@ -57,3 +67,8 @@ class VisualStabilityProvider @Inject constructor() { fun interface OnReorderingAllowedListener { fun onReorderingAllowed() } + +fun interface OnReorderingBannedListener { + fun onReorderingBanned() +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 91bb28e179b1..762c9a14d40e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -21,8 +21,8 @@ import android.content.Context; import android.service.notification.NotificationListenerService; import com.android.internal.jank.InteractionJankMonitor; -import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository; -import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepositoryImpl; +import com.android.settingslib.statusbar.notification.data.repository.ZenModeRepository; +import com.android.settingslib.statusbar.notification.data.repository.ZenModeRepositoryImpl; import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; @@ -279,19 +279,19 @@ public interface NotificationsModule { @Provides @SysUISingleton - public static NotificationsSoundPolicyRepository provideNotificationsSoundPolicyRepository( + static ZenModeRepository provideZenModeRepository( Context context, NotificationManager notificationManager, @Application CoroutineScope coroutineScope, @Background CoroutineContext coroutineContext) { - return new NotificationsSoundPolicyRepositoryImpl(context, notificationManager, + return new ZenModeRepositoryImpl(context, notificationManager, coroutineScope, coroutineContext); } @Provides @SysUISingleton - public static NotificationsSoundPolicyInteractor provideNotificationsSoundPolicyInteractror( - NotificationsSoundPolicyRepository repository) { + static NotificationsSoundPolicyInteractor provideNotificationsSoundPolicyInteractor( + ZenModeRepository repository) { return new NotificationsSoundPolicyInteractor(repository); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 190a2cd269ae..05d71967321f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2011,6 +2011,21 @@ public class ExpandableNotificationRow extends ActivatableNotificationView }; } + /** + * Retrieves an OnClickListener for the close button of a notification, which when invoked, + * dismisses the notificationc represented by the given ExpandableNotificationRow. + * + * @param row The ExpandableNotificationRow representing the notification to be dismissed. + * @return An OnClickListener instance that dismisses the notification(s) when invoked. + */ + public View.OnClickListener getCloseButtonOnClickListener(ExpandableNotificationRow row) { + return v -> { + if (row != null) { + row.performDismiss(false); + } + }; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure")); @@ -2935,24 +2950,21 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { return; } - float oldAlpha = getContentView().getAlpha(); if (!animated) { - mPublicLayout.animate().cancel(); - mPrivateLayout.animate().cancel(); - if (mChildrenContainer != null) { - mChildrenContainer.animate().cancel(); + if (!NotificationContentAlphaOptimization.isEnabled() + || mShowingPublic != oldShowingPublic) { + // Don't reset the alpha or cancel the animation if the showing layout doesn't + // change + mPublicLayout.animate().cancel(); + mPrivateLayout.animate().cancel(); + if (mChildrenContainer != null) { + mChildrenContainer.animate().cancel(); + } + resetAllContentAlphas(); } - resetAllContentAlphas(); mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); updateChildrenVisibility(); - if (NotificationContentAlphaOptimization.isEnabled()) { - // We want to set the old alpha to the now-showing layout to avoid breaking an - // on-going animation - if (oldAlpha != 1f) { - setAlphaAndLayerType(mShowingPublic ? mPublicLayout : mPrivateLayout, oldAlpha); - } - } } else { animateShowingPublic(delay, duration, mShowingPublic); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 6f00d96b6312..7fc331d6adab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -72,6 +72,8 @@ import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; import com.android.systemui.util.DumpUtilsKt; +import kotlinx.coroutines.DisposableHandle; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; @@ -109,6 +111,8 @@ public class NotificationContentView extends FrameLayout implements Notification private View mHeadsUpChild; private HybridNotificationView mSingleLineView; + @Nullable public DisposableHandle mContractedBinderHandle; + private RemoteInputView mExpandedRemoteInput; private RemoteInputView mHeadsUpRemoteInput; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index e70414084598..492d802c7cb2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -46,6 +46,10 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED +import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED +import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP +import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_SINGLELINE import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED @@ -62,6 +66,7 @@ import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarMo import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor +import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper @@ -86,6 +91,8 @@ constructor( private val remoteViewCache: NotifRemoteViewCache, private val remoteInputManager: NotificationRemoteInputManager, private val conversationProcessor: ConversationNotificationProcessor, + private val ronExtractor: RichOngoingNotificationContentExtractor, + private val ronInflater: RichOngoingNotificationViewInflater, @NotifInflation private val inflationExecutor: Executor, private val smartReplyStateInflater: SmartReplyStateInflater, private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, @@ -133,6 +140,8 @@ constructor( remoteViewCache, entry, conversationProcessor, + ronExtractor, + ronInflater, row, bindParams.isMinimized, bindParams.usesIncreasedHeight, @@ -177,6 +186,7 @@ constructor( notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, headsUpStyleProvider = headsUpStyleProvider, conversationProcessor = conversationProcessor, + ronExtractor = ronExtractor, logger = logger, ) inflateSmartReplyViews( @@ -255,39 +265,31 @@ constructor( ) { when (inflateFlag) { FLAG_CONTENT_VIEW_CONTRACTED -> - row.privateLayout.performWhenContentInactive( - NotificationContentView.VISIBLE_TYPE_CONTRACTED - ) { + row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_CONTRACTED) { + row.privateLayout.mContractedBinderHandle?.dispose() + row.privateLayout.mContractedBinderHandle = null row.privateLayout.setContractedChild(null) remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED) } FLAG_CONTENT_VIEW_EXPANDED -> - row.privateLayout.performWhenContentInactive( - NotificationContentView.VISIBLE_TYPE_EXPANDED - ) { + row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) { row.privateLayout.setExpandedChild(null) remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED) } FLAG_CONTENT_VIEW_HEADS_UP -> - row.privateLayout.performWhenContentInactive( - NotificationContentView.VISIBLE_TYPE_HEADSUP - ) { + row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) { row.privateLayout.setHeadsUpChild(null) remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP) row.privateLayout.setHeadsUpInflatedSmartReplies(null) } FLAG_CONTENT_VIEW_PUBLIC -> - row.publicLayout.performWhenContentInactive( - NotificationContentView.VISIBLE_TYPE_CONTRACTED - ) { + row.publicLayout.performWhenContentInactive(VISIBLE_TYPE_CONTRACTED) { row.publicLayout.setContractedChild(null) remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC) } FLAG_CONTENT_VIEW_SINGLE_LINE -> { if (AsyncHybridViewInflation.isEnabled) { - row.privateLayout.performWhenContentInactive( - NotificationContentView.VISIBLE_TYPE_SINGLELINE - ) { + row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_SINGLELINE) { row.privateLayout.setSingleLineView(null) } } @@ -308,32 +310,22 @@ constructor( @InflationFlag contentViews: Int ) { if (contentViews and FLAG_CONTENT_VIEW_CONTRACTED != 0) { - row.privateLayout.removeContentInactiveRunnable( - NotificationContentView.VISIBLE_TYPE_CONTRACTED - ) + row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED) } if (contentViews and FLAG_CONTENT_VIEW_EXPANDED != 0) { - row.privateLayout.removeContentInactiveRunnable( - NotificationContentView.VISIBLE_TYPE_EXPANDED - ) + row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_EXPANDED) } if (contentViews and FLAG_CONTENT_VIEW_HEADS_UP != 0) { - row.privateLayout.removeContentInactiveRunnable( - NotificationContentView.VISIBLE_TYPE_HEADSUP - ) + row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_HEADSUP) } if (contentViews and FLAG_CONTENT_VIEW_PUBLIC != 0) { - row.publicLayout.removeContentInactiveRunnable( - NotificationContentView.VISIBLE_TYPE_CONTRACTED - ) + row.publicLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED) } if ( AsyncHybridViewInflation.isEnabled && contentViews and FLAG_CONTENT_VIEW_SINGLE_LINE != 0 ) { - row.privateLayout.removeContentInactiveRunnable( - NotificationContentView.VISIBLE_TYPE_SINGLELINE - ) + row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE) } } @@ -353,6 +345,8 @@ constructor( private val remoteViewCache: NotifRemoteViewCache, private val entry: NotificationEntry, private val conversationProcessor: ConversationNotificationProcessor, + private val ronExtractor: RichOngoingNotificationContentExtractor, + private val ronInflater: RichOngoingNotificationViewInflater, private val row: ExpandableNotificationRow, private val isMinimized: Boolean, private val usesIncreasedHeight: Boolean, @@ -432,6 +426,7 @@ constructor( notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, headsUpStyleProvider = headsUpStyleProvider, conversationProcessor = conversationProcessor, + ronExtractor = ronExtractor, logger = logger ) logger.logAsyncTaskProgress( @@ -463,6 +458,21 @@ constructor( ) } } + + if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) { + logger.logAsyncTaskProgress(entry, "inflating RON view") + inflationProgress.richOngoingNotificationViewHolder = + inflationProgress.contentModel.richOngoingContentModel?.let { + ronInflater.inflateView( + contentModel = it, + existingView = row.privateLayout.contractedChild, + entry = entry, + systemUiContext = context, + parentView = row.privateLayout + ) + } + } + logger.logAsyncTaskProgress(entry, "getting row image resolver (on wrong thread!)") val imageResolver = row.imageResolver // wait for image resolver to finish preloading @@ -568,6 +578,7 @@ constructor( var inflatedSmartReplyState: InflatedSmartReplyState? = null var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null + var richOngoingNotificationViewHolder: InflatedContentViewHolder? = null // Inflated SingleLineView that lacks the UI State var inflatedSingleLineView: HybridNotificationView? = null @@ -602,6 +613,7 @@ constructor( val inflateHeadsUp = (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0 && result.remoteViews.headsUp != null) + if (inflateContracted || inflateExpanded || inflateHeadsUp) { logger.logAsyncTaskProgress(entry, "inflating contracted smart reply state") result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry) @@ -643,6 +655,7 @@ constructor( notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, headsUpStyleProvider: HeadsUpStyleProvider, conversationProcessor: ConversationNotificationProcessor, + ronExtractor: RichOngoingNotificationContentExtractor, logger: NotificationRowContentBinderLogger ): InflationProgress { // process conversations and extract the messaging style @@ -651,9 +664,24 @@ constructor( conversationProcessor.processNotification(entry, builder, logger) } else null + val richOngoingContentModel = + if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) { + ronExtractor.extractContentModel( + entry = entry, + builder = builder, + systemUIContext = systemUIContext, + packageContext = packageContext + ) + } else { + // if we're not re-inflating any RON views, make sure the model doesn't change + entry.richOngoingContentModel.value + } + + val remoteViewsFlags = getRemoteViewsFlags(reInflateFlags, richOngoingContentModel) + val remoteViews = createRemoteViews( - reInflateFlags = reInflateFlags, + reInflateFlags = remoteViewsFlags, builder = builder, isMinimized = isMinimized, usesIncreasedHeight = usesIncreasedHeight, @@ -688,6 +716,7 @@ constructor( NotificationContentModel( headsUpStatusBarModel = headsUpStatusBarModel, singleLineViewModel = singleLineViewModel, + richOngoingContentModel = richOngoingContentModel, ) return InflationProgress( @@ -815,7 +844,7 @@ constructor( val publicLayout = row.publicLayout val runningInflations = HashMap<Int, CancellationSignal>() var flag = FLAG_CONTENT_VIEW_CONTRACTED - if (reInflateFlags and flag != 0) { + if (reInflateFlags and flag != 0 && result.remoteViews.contracted != null) { val isNewView = !canReapplyRemoteView( newView = result.remoteViews.contracted, @@ -829,7 +858,7 @@ constructor( } override val remoteView: RemoteViews - get() = result.remoteViews.contracted!! + get() = result.remoteViews.contracted } logger.logAsyncTaskProgress(entry, "applying contracted view") applyRemoteView( @@ -847,104 +876,89 @@ constructor( callback = callback, parentLayout = privateLayout, existingView = privateLayout.contractedChild, - existingWrapper = - privateLayout.getVisibleWrapper( - NotificationContentView.VISIBLE_TYPE_CONTRACTED - ), + existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED), runningInflations = runningInflations, applyCallback = applyCallback, logger = logger ) } flag = FLAG_CONTENT_VIEW_EXPANDED - if (reInflateFlags and flag != 0) { - if (result.remoteViews.expanded != null) { - val isNewView = - !canReapplyRemoteView( - newView = result.remoteViews.expanded, - oldView = - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED) - ) - val applyCallback: ApplyCallback = - object : ApplyCallback() { - override fun setResultView(v: View) { - logger.logAsyncTaskProgress(entry, "expanded view applied") - result.inflatedExpandedView = v - } - - override val remoteView: RemoteViews - get() = result.remoteViews.expanded - } - logger.logAsyncTaskProgress(entry, "applying expanded view") - applyRemoteView( - inflationExecutor = inflationExecutor, - inflateSynchronously = inflateSynchronously, - isMinimized = isMinimized, - result = result, - reInflateFlags = reInflateFlags, - inflationId = flag, - remoteViewCache = remoteViewCache, - entry = entry, - row = row, - isNewView = isNewView, - remoteViewClickHandler = remoteViewClickHandler, - callback = callback, - parentLayout = privateLayout, - existingView = privateLayout.expandedChild, - existingWrapper = - privateLayout.getVisibleWrapper( - NotificationContentView.VISIBLE_TYPE_EXPANDED - ), - runningInflations = runningInflations, - applyCallback = applyCallback, - logger = logger + if (reInflateFlags and flag != 0 && result.remoteViews.expanded != null) { + val isNewView = + !canReapplyRemoteView( + newView = result.remoteViews.expanded, + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED) ) - } + val applyCallback: ApplyCallback = + object : ApplyCallback() { + override fun setResultView(v: View) { + logger.logAsyncTaskProgress(entry, "expanded view applied") + result.inflatedExpandedView = v + } + + override val remoteView: RemoteViews + get() = result.remoteViews.expanded + } + logger.logAsyncTaskProgress(entry, "applying expanded view") + applyRemoteView( + inflationExecutor = inflationExecutor, + inflateSynchronously = inflateSynchronously, + isMinimized = isMinimized, + result = result, + reInflateFlags = reInflateFlags, + inflationId = flag, + remoteViewCache = remoteViewCache, + entry = entry, + row = row, + isNewView = isNewView, + remoteViewClickHandler = remoteViewClickHandler, + callback = callback, + parentLayout = privateLayout, + existingView = privateLayout.expandedChild, + existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED), + runningInflations = runningInflations, + applyCallback = applyCallback, + logger = logger + ) } flag = FLAG_CONTENT_VIEW_HEADS_UP - if (reInflateFlags and flag != 0) { - if (result.remoteViews.headsUp != null) { - val isNewView = - !canReapplyRemoteView( - newView = result.remoteViews.headsUp, - oldView = - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP) - ) - val applyCallback: ApplyCallback = - object : ApplyCallback() { - override fun setResultView(v: View) { - logger.logAsyncTaskProgress(entry, "heads up view applied") - result.inflatedHeadsUpView = v - } - - override val remoteView: RemoteViews - get() = result.remoteViews.headsUp - } - logger.logAsyncTaskProgress(entry, "applying heads up view") - applyRemoteView( - inflationExecutor = inflationExecutor, - inflateSynchronously = inflateSynchronously, - isMinimized = isMinimized, - result = result, - reInflateFlags = reInflateFlags, - inflationId = flag, - remoteViewCache = remoteViewCache, - entry = entry, - row = row, - isNewView = isNewView, - remoteViewClickHandler = remoteViewClickHandler, - callback = callback, - parentLayout = privateLayout, - existingView = privateLayout.headsUpChild, - existingWrapper = - privateLayout.getVisibleWrapper( - NotificationContentView.VISIBLE_TYPE_HEADSUP - ), - runningInflations = runningInflations, - applyCallback = applyCallback, - logger = logger + if (reInflateFlags and flag != 0 && result.remoteViews.headsUp != null) { + val isNewView = + !canReapplyRemoteView( + newView = result.remoteViews.headsUp, + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP) ) - } + val applyCallback: ApplyCallback = + object : ApplyCallback() { + override fun setResultView(v: View) { + logger.logAsyncTaskProgress(entry, "heads up view applied") + result.inflatedHeadsUpView = v + } + + override val remoteView: RemoteViews + get() = result.remoteViews.headsUp + } + logger.logAsyncTaskProgress(entry, "applying heads up view") + applyRemoteView( + inflationExecutor = inflationExecutor, + inflateSynchronously = inflateSynchronously, + isMinimized = isMinimized, + result = result, + reInflateFlags = reInflateFlags, + inflationId = flag, + remoteViewCache = remoteViewCache, + entry = entry, + row = row, + isNewView = isNewView, + remoteViewClickHandler = remoteViewClickHandler, + callback = callback, + parentLayout = privateLayout, + existingView = privateLayout.headsUpChild, + existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP), + runningInflations = runningInflations, + applyCallback = applyCallback, + logger = logger + ) } flag = FLAG_CONTENT_VIEW_PUBLIC if (reInflateFlags and flag != 0) { @@ -979,10 +993,7 @@ constructor( callback = callback, parentLayout = publicLayout, existingView = publicLayout.contractedChild, - existingWrapper = - publicLayout.getVisibleWrapper( - NotificationContentView.VISIBLE_TYPE_CONTRACTED - ), + existingWrapper = publicLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED), runningInflations = runningInflations, applyCallback = applyCallback, logger = logger @@ -1359,79 +1370,34 @@ constructor( if (runningInflations.isNotEmpty()) { return false } - val privateLayout = row.privateLayout - val publicLayout = row.publicLayout logger.logAsyncTaskProgress(entry, "finishing") - if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) { - if (result.inflatedContentView != null) { - // New view case - privateLayout.setContractedChild(result.inflatedContentView) - remoteViewCache.putCachedView( - entry, - FLAG_CONTENT_VIEW_CONTRACTED, - result.remoteViews.contracted - ) - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) { - // Reinflation case. Only update if it's still cached (i.e. view has not been - // freed while inflating). - remoteViewCache.putCachedView( - entry, - FLAG_CONTENT_VIEW_CONTRACTED, - result.remoteViews.contracted - ) - } - } - if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) { - if (result.inflatedExpandedView != null) { - privateLayout.setExpandedChild(result.inflatedExpandedView) - remoteViewCache.putCachedView( - entry, - FLAG_CONTENT_VIEW_EXPANDED, - result.remoteViews.expanded - ) - } else if (result.remoteViews.expanded == null) { - privateLayout.setExpandedChild(null) - remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED) - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) { - remoteViewCache.putCachedView( - entry, - FLAG_CONTENT_VIEW_EXPANDED, - result.remoteViews.expanded - ) - } - if (result.remoteViews.expanded != null) { - privateLayout.setExpandedInflatedSmartReplies( - result.expandedInflatedSmartReplies - ) - } else { - privateLayout.setExpandedInflatedSmartReplies(null) - } - row.setExpandable(result.remoteViews.expanded != null) - } - if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) { - if (result.inflatedHeadsUpView != null) { - privateLayout.setHeadsUpChild(result.inflatedHeadsUpView) - remoteViewCache.putCachedView( - entry, - FLAG_CONTENT_VIEW_HEADS_UP, - result.remoteViews.headsUp - ) - } else if (result.remoteViews.headsUp == null) { - privateLayout.setHeadsUpChild(null) - remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP) - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) { - remoteViewCache.putCachedView( - entry, - FLAG_CONTENT_VIEW_HEADS_UP, - result.remoteViews.headsUp - ) - } - if (result.remoteViews.headsUp != null) { - privateLayout.setHeadsUpInflatedSmartReplies(result.headsUpInflatedSmartReplies) - } else { - privateLayout.setHeadsUpInflatedSmartReplies(null) - } + + // before updating the content model, stop existing binding if necessary + val hasRichOngoingContentModel = result.contentModel.richOngoingContentModel != null + val requestedRichOngoing = reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0 + val rejectedRichOngoing = requestedRichOngoing && !hasRichOngoingContentModel + if (result.richOngoingNotificationViewHolder != null || rejectedRichOngoing) { + row.privateLayout.mContractedBinderHandle?.dispose() + row.privateLayout.mContractedBinderHandle = null } + + // set the content model after disposal and before setting new rich ongoing view + entry.setContentModel(result.contentModel) + result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) } + + // set normal remote views (skipping rich ongoing states when that model exists) + val remoteViewsFlags = + getRemoteViewsFlags(reInflateFlags, result.contentModel.richOngoingContentModel) + setContentViewsFromRemoteViews( + remoteViewsFlags, + entry, + remoteViewCache, + result, + row, + isMinimized, + ) + + // set single line view if ( AsyncHybridViewInflation.isEnabled && reInflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE != 0 @@ -1444,78 +1410,142 @@ constructor( } else { SingleLineViewBinder.bind(viewModel, singleLineView) } - privateLayout.setSingleLineView(result.inflatedSingleLineView) + row.privateLayout.setSingleLineView(result.inflatedSingleLineView) } } - result.inflatedSmartReplyState?.let { privateLayout.setInflatedSmartReplyState(it) } - if (reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC != 0) { - if (result.inflatedPublicView != null) { - publicLayout.setContractedChild(result.inflatedPublicView) - remoteViewCache.putCachedView( - entry, - FLAG_CONTENT_VIEW_PUBLIC, - result.remoteViews.public - ) - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) { - remoteViewCache.putCachedView( - entry, - FLAG_CONTENT_VIEW_PUBLIC, - result.remoteViews.public - ) - } + + // after updating the content model, set the view, then start the new binder + result.richOngoingNotificationViewHolder?.let { viewHolder -> + row.privateLayout.contractedChild = viewHolder.view + row.privateLayout.expandedChild = null + row.privateLayout.headsUpChild = null + row.privateLayout.setExpandedInflatedSmartReplies(null) + row.privateLayout.setHeadsUpInflatedSmartReplies(null) + row.privateLayout.mContractedBinderHandle = + viewHolder.binder.setupContentViewBinder() + row.setExpandable(false) + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED) + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED) + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP) + } + + Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row)) + endListener?.onAsyncInflationFinished(entry) + return true + } + + private fun setContentViewsFromRemoteViews( + @InflationFlag reInflateFlags: Int, + entry: NotificationEntry, + remoteViewCache: NotifRemoteViewCache, + result: InflationProgress, + row: ExpandableNotificationRow, + isMinimized: Boolean, + ) { + val privateLayout = row.privateLayout + val publicLayout = row.publicLayout + val remoteViewsUpdater = RemoteViewsUpdater(reInflateFlags, entry, remoteViewCache) + remoteViewsUpdater.setContentView( + FLAG_CONTENT_VIEW_CONTRACTED, + result.remoteViews.contracted, + result.inflatedContentView, + privateLayout::setContractedChild + ) + remoteViewsUpdater.setContentView( + FLAG_CONTENT_VIEW_EXPANDED, + result.remoteViews.expanded, + result.inflatedExpandedView, + privateLayout::setExpandedChild + ) + remoteViewsUpdater.setSmartReplies( + FLAG_CONTENT_VIEW_EXPANDED, + result.remoteViews.expanded, + result.expandedInflatedSmartReplies, + privateLayout::setExpandedInflatedSmartReplies + ) + if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) { + row.setExpandable(result.remoteViews.expanded != null) } + remoteViewsUpdater.setContentView( + FLAG_CONTENT_VIEW_HEADS_UP, + result.remoteViews.headsUp, + result.inflatedHeadsUpView, + privateLayout::setHeadsUpChild + ) + remoteViewsUpdater.setSmartReplies( + FLAG_CONTENT_VIEW_HEADS_UP, + result.remoteViews.headsUp, + result.headsUpInflatedSmartReplies, + privateLayout::setHeadsUpInflatedSmartReplies + ) + remoteViewsUpdater.setContentView( + FLAG_CONTENT_VIEW_PUBLIC, + result.remoteViews.public, + result.inflatedPublicView, + publicLayout::setContractedChild + ) if (AsyncGroupHeaderViewInflation.isEnabled) { - if (reInflateFlags and FLAG_GROUP_SUMMARY_HEADER != 0) { - if (result.inflatedGroupHeaderView != null) { - // We need to set if the row is minimized before setting the group header to - // make sure the setting of header view works correctly - row.setIsMinimized(isMinimized) - row.setGroupHeader(/* headerView= */ result.inflatedGroupHeaderView) - remoteViewCache.putCachedView( - entry, - FLAG_GROUP_SUMMARY_HEADER, - result.remoteViews.normalGroupHeader - ) - } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) { + remoteViewsUpdater.setContentView( + FLAG_GROUP_SUMMARY_HEADER, + result.remoteViews.normalGroupHeader, + result.inflatedGroupHeaderView, + ) { views -> + row.setIsMinimized(isMinimized) + row.setGroupHeader(views) + } + remoteViewsUpdater.setContentView( + FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, + result.remoteViews.minimizedGroupHeader, + result.inflatedMinimizedGroupHeaderView, + ) { views -> + row.setIsMinimized(isMinimized) + row.setMinimizedGroupHeader(views) + } + } + } + + private class RemoteViewsUpdater( + @InflationFlag private val reInflateFlags: Int, + private val entry: NotificationEntry, + private val remoteViewCache: NotifRemoteViewCache, + ) { + fun <V : View> setContentView( + @InflationFlag flagState: Int, + remoteViews: RemoteViews?, + view: V?, + setView: (V?) -> Unit, + ) { + val clearViewFlags = FLAG_CONTENT_VIEW_HEADS_UP or FLAG_CONTENT_VIEW_EXPANDED + val shouldClearView = flagState and clearViewFlags != 0 + if (reInflateFlags and flagState != 0) { + if (view != null) { + setView(view) + remoteViewCache.putCachedView(entry, flagState, remoteViews) + } else if (shouldClearView && remoteViews == null) { + setView(null) + remoteViewCache.removeCachedView(entry, flagState) + } else if (remoteViewCache.hasCachedView(entry, flagState)) { // Re-inflation case. Only update if it's still cached (i.e. view has not // been freed while inflating). - remoteViewCache.putCachedView( - entry, - FLAG_GROUP_SUMMARY_HEADER, - result.remoteViews.normalGroupHeader - ) + remoteViewCache.putCachedView(entry, flagState, remoteViews) } } - if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) { - if (result.inflatedMinimizedGroupHeaderView != null) { - // We need to set if the row is minimized before setting the group header to - // make sure the setting of header view works correctly - row.setIsMinimized(isMinimized) - row.setMinimizedGroupHeader( - /* headerView= */ result.inflatedMinimizedGroupHeaderView - ) - remoteViewCache.putCachedView( - entry, - FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, - result.remoteViews.minimizedGroupHeader - ) - } else if ( - remoteViewCache.hasCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) - ) { - // Re-inflation case. Only update if it's still cached (i.e. view has not - // been freed while inflating). - remoteViewCache.putCachedView( - entry, - FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, - result.remoteViews.normalGroupHeader - ) + } + + fun setSmartReplies( + @InflationFlag flagState: Int, + remoteViews: RemoteViews?, + smartReplies: InflatedSmartReplyViewHolder?, + setSmartReplies: (InflatedSmartReplyViewHolder?) -> Unit, + ) { + if (reInflateFlags and flagState != 0) { + if (remoteViews != null) { + setSmartReplies(smartReplies) + } else { + setSmartReplies(null) } } } - entry.setContentModel(result.contentModel) - Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row)) - endListener?.onAsyncInflationFinished(entry) - return true } private fun createExpandedView( @@ -1562,6 +1592,21 @@ constructor( !oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED) } + @InflationFlag + private fun getRemoteViewsFlags( + @InflationFlag reInflateFlags: Int, + richOngoingContentModel: RichOngoingContentModel? + ): Int = + if (richOngoingContentModel != null) { + reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING.inv() + } else { + reInflateFlags + } + + @InflationFlag + private const val CONTENT_VIEWS_TO_CREATE_RICH_ONGOING = + FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP + private const val ASYNC_TASK_TRACE_METHOD = "NotificationRowContentBinderImpl.AsyncInflationTask" private const val APPLY_TRACE_METHOD = "NotificationRowContentBinderImpl#apply" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 84f2f6670839..c630c4d43fba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.row; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; +import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag; +import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent; import dagger.Binds; import dagger.Module; @@ -28,7 +30,7 @@ import javax.inject.Provider; /** * Dagger Module containing notification row and view inflation implementations. */ -@Module +@Module(subcomponents = {RichOngoingViewModelComponent.class}) public abstract class NotificationRowModule { /** @@ -47,6 +49,25 @@ public abstract class NotificationRowModule { } } + /** Provides ron content model extractor. */ + @Provides + @SysUISingleton + public static RichOngoingNotificationContentExtractor provideRonContentExtractor( + Provider<RichOngoingNotificationContentExtractorImpl> realImpl + ) { + if (RichOngoingNotificationFlag.isEnabled()) { + return realImpl.get(); + } else { + return new NoOpRichOngoingNotificationContentExtractor(); + } + } + + /** Provides ron view inflater. */ + @Binds + @SysUISingleton + public abstract RichOngoingNotificationViewInflater provideRonViewInflater( + RichOngoingNotificationViewInflaterImpl impl); + /** * Provides notification remote view cache instance. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt new file mode 100644 index 000000000000..b5ea861c19a6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.app.Notification +import android.app.PendingIntent +import android.content.Context +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.row.shared.IconModel +import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel +import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag +import com.android.systemui.statusbar.notification.row.shared.TimerContentModel +import java.time.Duration +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.ZoneId +import javax.inject.Inject + +/** + * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is + * applicable to the given style. + */ +interface RichOngoingNotificationContentExtractor { + fun extractContentModel( + entry: NotificationEntry, + builder: Notification.Builder, + systemUIContext: Context, + packageContext: Context + ): RichOngoingContentModel? +} + +class NoOpRichOngoingNotificationContentExtractor : RichOngoingNotificationContentExtractor { + override fun extractContentModel( + entry: NotificationEntry, + builder: Notification.Builder, + systemUIContext: Context, + packageContext: Context + ): RichOngoingContentModel? = null +} + +@SysUISingleton +class RichOngoingNotificationContentExtractorImpl @Inject constructor() : + RichOngoingNotificationContentExtractor { + + init { + /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode() + } + + override fun extractContentModel( + entry: NotificationEntry, + builder: Notification.Builder, + systemUIContext: Context, + packageContext: Context + ): RichOngoingContentModel? = + try { + val sbn = entry.sbn + val notification = sbn.notification + val icon = IconModel(notification.smallIcon) + if (sbn.packageName == "com.google.android.deskclock") { + when (notification.channelId) { + "Timers v2" -> { + parseTimerNotification(notification, icon) + } + "Stopwatch v2" -> { + Log.i("RONs", "Can't process stopwatch yet") + null + } + else -> { + Log.i("RONs", "Can't process channel '${notification.channelId}'") + null + } + } + } else null + } catch (e: Exception) { + Log.e("RONs", "Error parsing RON", e) + null + } + + /** + * FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available + * inside the sortKey of the clock app's timer notifications. + */ + private fun parseTimerNotification( + notification: Notification, + icon: IconModel + ): TimerContentModel { + // sortKey=1 0|↺7|RUNNING|▶16:21:58.523|Σ0:05:00|Δ0:00:03|⏳0:04:57 + // sortKey=1 0|↺7|PAUSED|Σ0:05:00|Δ0:04:54|⏳0:00:06 + // sortKey=1 1|↺7|RUNNING|▶16:30:28.433|Σ0:04:05|Δ0:00:06|⏳0:03:59 + // sortKey=1 0|↺7|RUNNING|▶16:36:18.350|Σ0:05:00|Δ0:01:42|⏳0:03:18 + // sortKey=1 2|↺7|RUNNING|▶16:38:37.816|Σ0:02:00|Δ0:01:09|⏳0:00:51 + // ▶ = "current" time (when updated) + // Σ = total time + // Δ = time elapsed + // ⏳ = time remaining + val sortKey = notification.sortKey + val (_, _, state, extra) = sortKey.split("|", limit = 4) + return when (state) { + "PAUSED" -> { + val (total, _, remaining) = extra.split("|") + val timeRemaining = parseTimeDelta(remaining) + TimerContentModel( + icon = icon, + name = total, + state = + TimerContentModel.TimerState.Paused( + timeRemaining = timeRemaining, + resumeIntent = notification.findActionWithName("Resume"), + resetIntent = notification.findActionWithName("Reset"), + ) + ) + } + "RUNNING" -> { + val (current, total, _, remaining) = extra.split("|") + val finishTime = parseCurrentTime(current) + parseTimeDelta(remaining).toMillis() + TimerContentModel( + icon = icon, + name = total, + state = + TimerContentModel.TimerState.Running( + finishTime = finishTime, + pauseIntent = notification.findActionWithName("Pause"), + addOneMinuteIntent = notification.findActionWithName("Add 1 min"), + ) + ) + } + else -> error("unknown state ($state) in sortKey=$sortKey") + } + } + + private fun Notification.findActionWithName(name: String): PendingIntent? { + return actions.firstOrNull { name == it.title?.toString() }?.actionIntent + } + + private fun parseCurrentTime(current: String): Long { + val (hour, minute, second, millis) = current.replace("▶", "").split(":", ".") + // NOTE: this won't work correctly at/around midnight. It's just for prototyping. + val localDateTime = + LocalDateTime.of( + LocalDate.now(), + LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000) + ) + val offset = ZoneId.systemDefault().rules.getOffset(localDateTime) + return localDateTime.toInstant(offset).toEpochMilli() + } + + private fun parseTimeDelta(delta: String): Duration { + val (hour, minute, second) = delta.replace("Σ", "").replace("⏳", "").split(":") + return Duration.ofHours(hour.toLong()) + .plusMinutes(minute.toLong()) + .plusSeconds(second.toLong()) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt new file mode 100644 index 000000000000..e9c4960a4011 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.app.Notification +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel +import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag +import com.android.systemui.statusbar.notification.row.shared.StopwatchContentModel +import com.android.systemui.statusbar.notification.row.shared.TimerContentModel +import com.android.systemui.statusbar.notification.row.ui.view.TimerView +import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder +import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent +import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel +import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle + +fun interface DeferredContentViewBinder { + fun setupContentViewBinder(): DisposableHandle +} + +class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder) + +/** + * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is + * applicable to the given style. + */ +interface RichOngoingNotificationViewInflater { + fun inflateView( + contentModel: RichOngoingContentModel, + existingView: View?, + entry: NotificationEntry, + systemUiContext: Context, + parentView: ViewGroup, + ): InflatedContentViewHolder? +} + +@SysUISingleton +class RichOngoingNotificationViewInflaterImpl +@Inject +constructor( + private val viewModelComponentFactory: RichOngoingViewModelComponent.Factory, +) : RichOngoingNotificationViewInflater { + + override fun inflateView( + contentModel: RichOngoingContentModel, + existingView: View?, + entry: NotificationEntry, + systemUiContext: Context, + parentView: ViewGroup, + ): InflatedContentViewHolder? { + if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return null + val component = viewModelComponentFactory.create(entry) + return when (contentModel) { + is TimerContentModel -> + inflateTimerView( + existingView, + component::createTimerViewModel, + systemUiContext, + parentView + ) + is StopwatchContentModel -> TODO("Not yet implemented") + } + } + + private fun inflateTimerView( + existingView: View?, + createViewModel: () -> TimerViewModel, + systemUiContext: Context, + parentView: ViewGroup, + ): InflatedContentViewHolder? { + if (existingView is TimerView && !existingView.isReinflateNeeded()) return null + val newView = + LayoutInflater.from(systemUiContext) + .inflate( + R.layout.rich_ongoing_timer_notification, + parentView, + /* attachToRoot= */ false + ) as TimerView + return InflatedContentViewHolder(newView) { + TimerViewBinder.bindWhileAttached(newView, createViewModel()) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt index 6fc82c9bcd7e..463192cd7042 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt @@ -244,7 +244,7 @@ internal object SingleLineViewInflater { return SingleIcon(null) } val userKey = user.getKeyOrName() - var conversationIcon: Icon? = null + var conversationIcon: Icon? = shortcutIcon var conversationText: CharSequence? = conversationTitle val groups = groupMessages(messages, historicMessages) @@ -253,10 +253,6 @@ internal object SingleLineViewInflater { if (!isGroupConversation) { // Conversation is one-to-one, load the single icon // Let's resolve the icon / text from the last sender - if (shortcutIcon != null) { - conversationIcon = shortcutIcon - } - for (i in messages.lastIndex downTo 0) { val message = messages[i] val sender = message.senderPerson diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt new file mode 100644 index 000000000000..bac887ba7f14 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepository.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.data.repository + +import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel +import kotlinx.coroutines.flow.StateFlow + +/** A repository of states relating to a specific notification row. */ +interface NotificationRowRepository { + /** + * A flow of an immutable data class with the current state of the Rich Ongoing Notification + * content, if applicable. + */ + val richOngoingContentModel: StateFlow<RichOngoingContentModel?> +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt new file mode 100644 index 000000000000..4705ace7fac6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.row.domain.interactor + +import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository +import com.android.systemui.statusbar.notification.row.shared.TimerContentModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterIsInstance + +/** Interactor specific to a particular notification row. */ +class NotificationRowInteractor @Inject constructor(repository: NotificationRowRepository) { + /** Content of a rich ongoing timer notification. */ + val timerContentModel: Flow<TimerContentModel> = + repository.richOngoingContentModel.filterIsInstance<TimerContentModel>() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt new file mode 100644 index 000000000000..e61193892d10 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/IconModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.shared + +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon + +// TODO: figure out how to support lazy resolution of the drawable, e.g. on unrelated text change +class IconModel(val icon: Icon) { + var drawable: Drawable? = null + + override fun equals(other: Any?): Boolean = + when (other) { + null -> false + (other === this) -> true + !is IconModel -> false + else -> other.icon.sameAs(icon) + } + + override fun toString(): String = "IconModel(icon=$icon, drawable=$drawable)" +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt index b2421bc72d00..46010a1ab43d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt @@ -21,4 +21,7 @@ import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineVi data class NotificationContentModel( val headsUpStatusBarModel: HeadsUpStatusBarModel, val singleLineViewModel: SingleLineViewModel? = null, + val richOngoingContentModel: RichOngoingContentModel? = null, ) + +sealed interface RichOngoingContentModel diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt new file mode 100644 index 000000000000..558470175e8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingClock.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.shared + +import android.app.PendingIntent +import java.time.Duration + +/** + * Represents a simple timer that counts down to a time. + * + * @param name the label for the timer + * @param state state of the timer, including time and whether it is paused or running + */ +data class TimerContentModel( + val icon: IconModel, + val name: String, + val state: TimerState, +) : RichOngoingContentModel { + /** The state (paused or running) of the timer, and relevant time */ + sealed interface TimerState { + /** + * Indicates a running timer + * + * @param finishTime the time in ms since epoch that the timer will finish + * @param pauseIntent the action for pausing the timer + */ + data class Running( + val finishTime: Long, + val pauseIntent: PendingIntent?, + val addOneMinuteIntent: PendingIntent?, + ) : TimerState + + /** + * Indicates a paused timer + * + * @param timeRemaining the time in ms remaining on the paused timer + * @param resumeIntent the action for resuming the timer + */ + data class Paused( + val timeRemaining: Duration, + val resumeIntent: PendingIntent?, + val resetIntent: PendingIntent?, + ) : TimerState + } +} + +/** + * Represents a simple stopwatch that counts up and allows tracking laps. + * + * @param state state of the stopwatch, including time and whether it is paused or running + * @param lapDurations a list of durations of each completed lap + */ +data class StopwatchContentModel( + val icon: IconModel, + val state: StopwatchState, + val lapDurations: List<Long>, +) : RichOngoingContentModel { + /** The state (paused or running) of the stopwatch, and relevant time */ + sealed interface StopwatchState { + /** + * Indicates a running stopwatch + * + * @param startTime the time in ms since epoch that the stopwatch started, plus any + * accumulated pause time + * @param pauseIntent the action for pausing the stopwatch + */ + data class Running( + val startTime: Long, + val pauseIntent: PendingIntent, + ) : StopwatchState + + /** + * Indicates a paused stopwatch + * + * @param timeElapsed the time in ms elapsed on the stopwatch + * @param resumeIntent the action for resuming the stopwatch + */ + data class Paused( + val timeElapsed: Duration, + val resumeIntent: PendingIntent, + ) : StopwatchState + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt new file mode 100644 index 000000000000..4a7f7cd46ad1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/RichOngoingNotificationFlag.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.shared + +import android.app.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the api rich ongoing flag state. */ +@Suppress("NOTHING_TO_INLINE") +object RichOngoingNotificationFlag { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_API_RICH_ONGOING + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.apiRichOngoing() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt new file mode 100644 index 000000000000..95c507cb72a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/ConfigurationTracker.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.ui.view + +import android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS +import android.content.pm.ActivityInfo.CONFIG_DENSITY +import android.content.pm.ActivityInfo.CONFIG_FONT_SCALE +import android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION +import android.content.pm.ActivityInfo.CONFIG_LOCALE +import android.content.pm.ActivityInfo.CONFIG_UI_MODE +import android.content.res.Configuration +import android.content.res.Resources + +/** + * Tracks the active configuration when constructed and returns (when queried) whether the + * configuration has unhandled changes. + */ +class ConfigurationTracker( + private val resources: Resources, + private val unhandledConfigChanges: Int +) { + private val initialConfig = Configuration(resources.configuration) + + constructor( + resources: Resources, + handlesDensityFontScale: Boolean = false, + handlesTheme: Boolean = false, + handlesLocaleAndLayout: Boolean = true, + ) : this( + resources, + unhandledConfigChanges = + (if (handlesDensityFontScale) 0 else CONFIG_DENSITY or CONFIG_FONT_SCALE) or + (if (handlesTheme) 0 else CONFIG_ASSETS_PATHS or CONFIG_UI_MODE) or + (if (handlesLocaleAndLayout) 0 else CONFIG_LOCALE or CONFIG_LAYOUT_DIRECTION) + ) + + /** + * Whether the current configuration has unhandled changes relative to the initial configuration + */ + fun hasUnhandledConfigChange(): Boolean = + initialConfig.diff(resources.configuration) and unhandledConfigChanges != 0 +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt new file mode 100644 index 000000000000..0d83aced6d07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerButtonView.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.ui.view + +import android.annotation.DrawableRes +import android.content.Context +import android.util.AttributeSet +import android.widget.Button + +class TimerButtonView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0, +) : Button(context, attrs, defStyleAttr, defStyleRes) { + + private val Int.dp: Int + get() = (this * context.resources.displayMetrics.density).toInt() + + fun setIcon(@DrawableRes icon: Int) { + val drawable = context.getDrawable(icon) + drawable?.setBounds(0, 0, 24.dp, 24.dp) + setCompoundDrawablesRelative(drawable, null, null, null) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt new file mode 100644 index 000000000000..2e164d60431d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/TimerView.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.ui.view + +import android.content.Context +import android.graphics.drawable.Drawable +import android.os.SystemClock +import android.util.AttributeSet +import android.widget.Chronometer +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import com.android.systemui.res.R + +class TimerView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0, +) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { + + private val configTracker = ConfigurationTracker(resources) + + private lateinit var icon: ImageView + private lateinit var label: TextView + private lateinit var chronometer: Chronometer + private lateinit var pausedTimeRemaining: TextView + lateinit var mainButton: TimerButtonView + private set + + lateinit var altButton: TimerButtonView + private set + + override fun onFinishInflate() { + super.onFinishInflate() + icon = requireViewById(R.id.icon) + label = requireViewById(R.id.label) + chronometer = requireViewById(R.id.chronoRemaining) + pausedTimeRemaining = requireViewById(R.id.pausedTimeRemaining) + mainButton = requireViewById(R.id.mainButton) + altButton = requireViewById(R.id.altButton) + } + + /** the resources configuration has changed such that the view needs to be reinflated */ + fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange() + + fun setIcon(iconDrawable: Drawable?) { + this.icon.setImageDrawable(iconDrawable) + } + + fun setLabel(label: String) { + this.label.text = label + } + + fun setPausedTime(pausedTime: String?) { + if (pausedTime != null) { + pausedTimeRemaining.text = pausedTime + pausedTimeRemaining.isVisible = true + } else { + pausedTimeRemaining.isVisible = false + } + } + + fun setCountdownTime(countdownTimeMs: Long?) { + if (countdownTimeMs != null) { + chronometer.base = + countdownTimeMs - System.currentTimeMillis() + SystemClock.elapsedRealtime() + chronometer.isVisible = true + chronometer.start() + } else { + chronometer.isVisible = false + chronometer.stop() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt new file mode 100644 index 000000000000..c9ff58961582 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/TimerViewBinder.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.ui.viewbinder + +import android.view.View +import androidx.core.view.isGone +import androidx.lifecycle.lifecycleScope +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.notification.row.ui.view.TimerButtonView +import com.android.systemui.statusbar.notification.row.ui.view.TimerView +import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch + +/** Binds a [TimerView] to its [view model][TimerViewModel]. */ +object TimerViewBinder { + fun bindWhileAttached( + view: TimerView, + viewModel: TimerViewModel, + ): DisposableHandle { + return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } } + } + + suspend fun bind( + view: TimerView, + viewModel: TimerViewModel, + ) = coroutineScope { + launch { viewModel.icon.collect { view.setIcon(it) } } + launch { viewModel.label.collect { view.setLabel(it) } } + launch { viewModel.pausedTime.collect { view.setPausedTime(it) } } + launch { viewModel.countdownTime.collect { view.setCountdownTime(it) } } + launch { viewModel.mainButtonModel.collect { bind(view.mainButton, it) } } + launch { viewModel.altButtonModel.collect { bind(view.altButton, it) } } + } + + fun bind(buttonView: TimerButtonView, model: TimerViewModel.ButtonViewModel?) { + if (model != null) { + buttonView.setIcon(model.iconRes) + buttonView.setText(model.labelRes) + buttonView.setOnClickListener( + model.pendingIntent?.let { pendingIntent -> + View.OnClickListener { pendingIntent.send() } + } + ) + buttonView.isEnabled = model.pendingIntent != null + } + buttonView.isGone = model == null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt new file mode 100644 index 000000000000..dad52a3b2c45 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.ui.viewmodel + +// noinspection CleanArchitectureDependencyViolation +import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository +import dagger.BindsInstance +import dagger.Subcomponent + +@Subcomponent +interface RichOngoingViewModelComponent { + + @Subcomponent.Factory + interface Factory { + /** Creates an instance of [RichOngoingViewModelComponent]. */ + fun create( + @BindsInstance repository: NotificationRowRepository + ): RichOngoingViewModelComponent + } + + fun createTimerViewModel(): TimerViewModel +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt new file mode 100644 index 000000000000..a85c87f288d3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModel.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.ui.viewmodel + +import android.annotation.DrawableRes +import android.annotation.StringRes +import android.app.PendingIntent +import android.graphics.drawable.Drawable +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor +import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag +import com.android.systemui.statusbar.notification.row.shared.TimerContentModel.TimerState +import com.android.systemui.util.kotlin.FlowDumperImpl +import java.time.Duration +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull + +/** A view model for Timer notifications. */ +class TimerViewModel +@Inject +constructor( + dumpManager: DumpManager, + rowInteractor: NotificationRowInteractor, +) : FlowDumperImpl(dumpManager) { + init { + /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode() + } + + private val state: Flow<TimerState> = rowInteractor.timerContentModel.mapNotNull { it.state } + + val icon: Flow<Drawable?> = rowInteractor.timerContentModel.mapNotNull { it.icon.drawable } + + val label: Flow<String> = rowInteractor.timerContentModel.mapNotNull { it.name } + + val countdownTime: Flow<Long?> = state.map { (it as? TimerState.Running)?.finishTime } + + val pausedTime: Flow<String?> = + state.map { (it as? TimerState.Paused)?.timeRemaining?.format() } + + val mainButtonModel: Flow<ButtonViewModel> = + state.map { + when (it) { + is TimerState.Paused -> + ButtonViewModel( + it.resumeIntent, + com.android.systemui.res.R.string.controls_media_resume, // "Resume", + com.android.systemui.res.R.drawable.ic_media_play + ) + is TimerState.Running -> + ButtonViewModel( + it.pauseIntent, + com.android.systemui.res.R.string.controls_media_button_pause, // "Pause", + com.android.systemui.res.R.drawable.ic_media_pause + ) + } + } + + val altButtonModel: Flow<ButtonViewModel?> = + state.map { + when (it) { + is TimerState.Paused -> + it.resetIntent?.let { resetIntent -> + ButtonViewModel( + resetIntent, + com.android.systemui.res.R.string.reset, // "Reset", + com.android.systemui.res.R.drawable.ic_close_white_rounded + ) + } + is TimerState.Running -> + it.addOneMinuteIntent?.let { addOneMinuteIntent -> + ButtonViewModel( + addOneMinuteIntent, + com.android.systemui.res.R.string.add, // "Add 1 minute", + com.android.systemui.res.R.drawable.ic_add + ) + } + } + } + + data class ButtonViewModel( + val pendingIntent: PendingIntent?, + @StringRes val labelRes: Int, + @DrawableRes val iconRes: Int, + ) +} + +private fun Duration.format(): String { + val hours = this.toHours() + return if (hours > 0) { + String.format("%d:%02d:%02d", hours, toMinutesPart(), toSecondsPart()) + } else { + String.format("%d:%02d", toMinutes(), toSecondsPart()) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 939424945038..f3521234e67a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -36,6 +36,7 @@ import androidx.annotation.Nullable; import com.android.app.animation.Interpolators; import com.android.internal.widget.CachingIconView; +import com.android.internal.widget.NotificationCloseButton; import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.res.R; import com.android.systemui.statusbar.TransformableView; @@ -60,6 +61,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple = new PathInterpolator(0.4f, 0f, 0.7f, 1f); protected final ViewTransformationHelper mTransformationHelper; private CachingIconView mIcon; + private NotificationCloseButton mCloseButton; private NotificationExpandButton mExpandButton; private View mAltExpandTarget; private View mIconContainer; @@ -112,6 +114,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple TRANSFORMING_VIEW_TITLE); resolveHeaderViews(); addFeedbackOnClickListener(row); + addCloseButtonOnClickListener(row); } @Override @@ -150,6 +153,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple mNotificationTopLine = mView.findViewById(com.android.internal.R.id.notification_top_line); mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon); mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback); + mCloseButton = mView.findViewById(com.android.internal.R.id.close_button); } private void addFeedbackOnClickListener(ExpandableNotificationRow row) { @@ -179,6 +183,13 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple } } + private void addCloseButtonOnClickListener(ExpandableNotificationRow row) { + View.OnClickListener listener = row.getCloseButtonOnClickListener(row); + if (mCloseButton != null && listener != null) { + mCloseButton.setOnClickListener(listener); + } + } + @Override public void onContentUpdated(ExpandableNotificationRow row) { super.onContentUpdated(row); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 5f4e832f31a3..fbddc060bcee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -29,6 +29,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; @@ -63,6 +64,8 @@ public class AmbientState implements Dumpable { * Used to read bouncer states. */ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private float mStackTop; + private float mStackCutoff; private int mScrollY; private float mOverScrollTopAmount; private float mOverScrollBottomAmount; @@ -128,13 +131,13 @@ public class AmbientState implements Dumpable { /** Distance of top of notifications panel from top of screen. */ private float mStackY = 0; - /** Height of notifications panel. */ + /** Height of notifications panel interpolated by the expansion fraction. */ private float mStackHeight = 0; /** Fraction of shade expansion. */ private float mExpansionFraction; - /** Height of the notifications panel without top padding when expansion completes. */ + /** Height of the notifications panel when expansion completes. */ private float mStackEndHeight; /** Whether we are swiping up. */ @@ -173,8 +176,7 @@ public class AmbientState implements Dumpable { } /** - * @param stackEndHeight Height of the notifications panel without top padding - * when expansion completes. + * @see #getStackEndHeight() */ public void setStackEndHeight(float stackEndHeight) { mStackEndHeight = stackEndHeight; @@ -184,6 +186,7 @@ public class AmbientState implements Dumpable { * @param stackY Distance of top of notifications panel from top of screen. */ public void setStackY(float stackY) { + SceneContainerFlag.assertInLegacyMode(); mStackY = stackY; } @@ -191,6 +194,7 @@ public class AmbientState implements Dumpable { * @return Distance of top of notifications panel from top of screen. */ public float getStackY() { + SceneContainerFlag.assertInLegacyMode(); return mStackY; } @@ -252,14 +256,14 @@ public class AmbientState implements Dumpable { } /** - * @param stackHeight Height of notifications panel. + * @see #getStackHeight() */ public void setStackHeight(float stackHeight) { mStackHeight = stackHeight; } /** - * @return Height of notifications panel. + * @return Height of notifications panel interpolated by the expansion fraction. */ public float getStackHeight() { return mStackHeight; @@ -346,6 +350,33 @@ public class AmbientState implements Dumpable { return mZDistanceBetweenElements; } + /** Y coordinate in view pixels of the top of the notification stack */ + public float getStackTop() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + return mStackTop; + } + + /** @see #getStackTop() */ + public void setStackTop(float mStackTop) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + this.mStackTop = mStackTop; + } + + /** + * Y coordinate in view pixels above which the bottom of the notification stack / shelf / footer + * must be. + */ + public float getStackCutoff() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + return mStackCutoff; + } + + /** @see #getStackCutoff() */ + public void setStackCutoff(float stackCutoff) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + this.mStackCutoff = stackCutoff; + } + public int getScrollY() { return mScrollY; } @@ -752,6 +783,8 @@ public class AmbientState implements Dumpable { @Override public void dump(PrintWriter pw, String[] args) { + pw.println("mStackTop=" + mStackTop); + pw.println("mStackCutoff" + mStackCutoff); pw.println("mTopPadding=" + mTopPadding); pw.println("mStackTopMargin=" + mStackTopMargin); pw.println("mStackTranslation=" + mStackTranslation); 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 71a0b941c350..d54e66ee3525 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 @@ -834,11 +834,11 @@ public class NotificationStackScrollLayout drawDebugInfo(canvas, y, Color.RED, /* label= */ "y = " + y); if (SceneContainerFlag.isEnabled()) { - y = (int) mScrollViewFields.getStackTop(); + y = (int) mAmbientState.getStackTop(); drawDebugInfo(canvas, y, Color.RED, /* label= */ "getStackTop() = " + y); - y = (int) mScrollViewFields.getStackBottom(); - drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "getStackBottom() = " + y); + y = (int) mAmbientState.getStackCutoff(); + drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "getStackCutoff() = " + y); y = (int) mScrollViewFields.getHeadsUpTop(); drawDebugInfo(canvas, y, Color.GREEN, /* label= */ "getHeadsUpTop() = " + y); @@ -1181,9 +1181,11 @@ public class NotificationStackScrollLayout updateAlgorithmLayoutMinHeight(); updateOwnTranslationZ(); - // Give The Algorithm information regarding the QS height so it can layout notifications - // properly. Needed for some devices that grows notifications down-to-top - mStackScrollAlgorithm.updateQSFrameTop(mQsHeader == null ? 0 : mQsHeader.getHeight()); + if (!SceneContainerFlag.isEnabled()) { + // Give The Algorithm information regarding the QS height so it can layout notifications + // properly. Needed for some devices that grows notifications down-to-top + mStackScrollAlgorithm.updateQSFrameTop(mQsHeader == null ? 0 : mQsHeader.getHeight()); + } // Once the layout has finished, we don't need to animate any scrolling clampings anymore. mAnimateStackYForContentHeightChange = false; @@ -1214,14 +1216,14 @@ public class NotificationStackScrollLayout @Override public void setStackTop(float stackTop) { - mScrollViewFields.setStackTop(stackTop); + mAmbientState.setStackTop(stackTop); // TODO(b/332574413): replace the following with using stackTop updateTopPadding(stackTop, isAddOrRemoveAnimationPending()); } @Override - public void setStackBottom(float stackBottom) { - mScrollViewFields.setStackBottom(stackBottom); + public void setStackCutoff(float stackCutoff) { + mAmbientState.setStackCutoff(stackCutoff); } @Override @@ -1424,11 +1426,7 @@ public class NotificationStackScrollLayout if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) { fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction); } - // TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL - if (SceneContainerFlag.isEnabled()) { - // stackY should be driven by scene container, not NSSL - mAmbientState.setStackY(getTopPadding()); - } else { + if (!SceneContainerFlag.isEnabled()) { final float stackY = MathUtils.lerp(0, endTopPosition, fraction); mAmbientState.setStackY(stackY); } @@ -1442,22 +1440,40 @@ public class NotificationStackScrollLayout @VisibleForTesting public void updateStackEndHeightAndStackHeight(float fraction) { final float oldStackHeight = mAmbientState.getStackHeight(); - if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) { - final float endHeight = updateStackEndHeight( - getHeight(), getEmptyBottomMargin(), getTopPadding()); + if (SceneContainerFlag.isEnabled()) { + final float endHeight; + if (!shouldSkipHeightUpdate()) { + endHeight = updateStackEndHeight(); + } else { + endHeight = mAmbientState.getStackEndHeight(); + } updateStackHeight(endHeight, fraction); } else { - // Always updateStackHeight to prevent jumps in the stack height when this fraction - // suddenly reapplies after a freeze. - final float endHeight = mAmbientState.getStackEndHeight(); - updateStackHeight(endHeight, fraction); + if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) { + final float endHeight = updateStackEndHeight( + getHeight(), getEmptyBottomMargin(), getTopPadding()); + updateStackHeight(endHeight, fraction); + } else { + // Always updateStackHeight to prevent jumps in the stack height when this fraction + // suddenly reapplies after a freeze. + final float endHeight = mAmbientState.getStackEndHeight(); + updateStackHeight(endHeight, fraction); + } } if (oldStackHeight != mAmbientState.getStackHeight()) { requestChildrenUpdate(); } } + private float updateStackEndHeight() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + float height = Math.max(0f, mAmbientState.getStackCutoff() - mAmbientState.getStackTop()); + mAmbientState.setStackEndHeight(height); + return height; + } + private float updateStackEndHeight(float height, float bottomMargin, float topPadding) { + SceneContainerFlag.assertInLegacyMode(); final float stackEndHeight; if (mMaxDisplayedNotifications != -1) { // The stack intrinsic height already contains the correct value when there is a limit @@ -1811,6 +1827,7 @@ public class NotificationStackScrollLayout } public void setQsHeader(ViewGroup qsHeader) { + SceneContainerFlag.assertInLegacyMode(); mQsHeader = qsHeader; } @@ -2662,6 +2679,7 @@ public class NotificationStackScrollLayout } public void setMaxTopPadding(int maxTopPadding) { + SceneContainerFlag.assertInLegacyMode(); mMaxTopPadding = maxTopPadding; } @@ -2682,6 +2700,7 @@ public class NotificationStackScrollLayout } public float getTopPaddingOverflow() { + SceneContainerFlag.assertInLegacyMode(); return mTopPaddingOverflow; } @@ -3721,7 +3740,7 @@ public class NotificationStackScrollLayout protected boolean isInsideQsHeader(MotionEvent ev) { if (SceneContainerFlag.isEnabled()) { - return ev.getY() < mScrollViewFields.getStackTop(); + return ev.getY() < mAmbientState.getStackTop(); } mQsHeader.getBoundsOnScreen(mQsHeaderBound); @@ -4641,6 +4660,7 @@ public class NotificationStackScrollLayout } public boolean isEmptyShadeViewVisible() { + SceneContainerFlag.assertInLegacyMode(); return mEmptyShadeView.isVisible(); } @@ -4919,6 +4939,7 @@ public class NotificationStackScrollLayout } public void setQsFullScreen(boolean qsFullScreen) { + SceneContainerFlag.assertInLegacyMode(); if (FooterViewRefactor.isEnabled()) { if (qsFullScreen == mQsFullScreen) { return; // no change @@ -5095,6 +5116,7 @@ public class NotificationStackScrollLayout } public void setExpandingVelocity(float expandingVelocity) { + SceneContainerFlag.assertInLegacyMode(); mAmbientState.setExpandingVelocity(expandingVelocity); } 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 6a3055f4b4cd..d9846850fc71 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 @@ -68,7 +68,6 @@ import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -1320,7 +1319,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { updateAlpha(); } - void setMaxAlphaFromView(float alpha) { + /** + * Max alpha from the containing view. Used by brightness slider as an example. + */ + public void setMaxAlphaFromView(float alpha) { mMaxAlphaFromView = alpha; updateAlpha(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index 6afcf372b039..97ec39192cc8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -32,13 +32,6 @@ import java.util.function.Consumer class ScrollViewFields { /** Used to produce the clipping path */ var scrimClippingShape: ShadeScrimShape? = null - /** Y coordinate in view pixels of the top of the notification stack */ - var stackTop: Float = 0f - /** - * Y coordinate in view pixels above which the bottom of the notification stack / shelf / footer - * must be. - */ - var stackBottom: Float = 0f /** Y coordinate in view pixels of the top of the HUN */ var headsUpTop: Float = 0f /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */ @@ -70,17 +63,17 @@ class ScrollViewFields { /** send the [syntheticScroll] to the [syntheticScrollConsumer], if present. */ fun sendSyntheticScroll(syntheticScroll: Float) = syntheticScrollConsumer?.accept(syntheticScroll) + /** send [isCurrentGestureOverscroll] to the [currentGestureOverscrollConsumer], if present. */ fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) = currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll) + /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) fun dump(pw: IndentingPrintWriter) { pw.printSection("StackViewStates") { pw.println("scrimClippingShape", scrimClippingShape) - pw.println("stackTop", stackTop) - pw.println("stackBottom", stackBottom) pw.println("headsUpTop", headsUpTop) pw.println("isScrolledToTop", isScrolledToTop) } 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 f9efc07c67fb..ee7b5c48a3e7 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.internal.policy.SystemBarUtils; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; @@ -332,8 +333,10 @@ public class StackScrollAlgorithm { private void updateClipping(StackScrollAlgorithmState algorithmState, AmbientState ambientState) { - float drawStart = ambientState.isOnKeyguard() ? 0 + float stackTop = SceneContainerFlag.isEnabled() ? ambientState.getStackTop() : ambientState.getStackY() - ambientState.getScrollY(); + float drawStart = ambientState.isOnKeyguard() ? 0 + : stackTop; float clipStart = 0; int childCount = algorithmState.visibleChildren.size(); boolean firstHeadsUp = true; @@ -442,12 +445,26 @@ public class StackScrollAlgorithm { state.visibleChildren.clear(); state.visibleChildren.ensureCapacity(childCount); int notGoneIndex = 0; + boolean emptyShadeVisible = false; for (int i = 0; i < childCount; i++) { ExpandableView v = (ExpandableView) mHostView.getChildAt(i); if (v.getVisibility() != View.GONE) { if (v == ambientState.getShelf()) { continue; } + if (FooterViewRefactor.isEnabled()) { + if (v instanceof EmptyShadeView) { + emptyShadeVisible = true; + } + if (v instanceof FooterView) { + if (emptyShadeVisible || notGoneIndex == 0) { + // if the empty shade is visible or the footer is the first visible + // view, we're in a transitory state so let's leave the footer alone. + continue; + } + } + } + notGoneIndex = updateNotGoneIndex(state, notGoneIndex, v); if (v instanceof ExpandableNotificationRow row) { @@ -641,7 +658,10 @@ public class StackScrollAlgorithm { // Incoming views have yTranslation=0 by default. viewState.setYTranslation(algorithmState.mCurrentYPosition); - float viewEnd = viewState.getYTranslation() + viewState.height + ambientState.getStackY(); + float stackTop = SceneContainerFlag.isEnabled() + ? ambientState.getStackTop() + : ambientState.getStackY(); + float viewEnd = stackTop + viewState.getYTranslation() + viewState.height; maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(), view.mustStayOnScreen(), /* topVisible= */ viewState.getYTranslation() >= mNotificationScrimPadding, @@ -681,7 +701,9 @@ public class StackScrollAlgorithm { } } else { if (view instanceof EmptyShadeView) { - float fullHeight = ambientState.getLayoutMaxHeight() + mMarginBottom + float fullHeight = SceneContainerFlag.isEnabled() + ? ambientState.getStackCutoff() - ambientState.getStackTop() + : ambientState.getLayoutMaxHeight() + mMarginBottom - ambientState.getStackY(); viewState.setYTranslation((fullHeight - getMaxAllowedChildHeight(view)) / 2f); } else if (view != ambientState.getTrackedHeadsUpRow()) { @@ -726,7 +748,7 @@ public class StackScrollAlgorithm { + mPaddingBetweenElements; setLocation(view.getViewState(), algorithmState.mCurrentYPosition, i); - viewState.setYTranslation(viewState.getYTranslation() + ambientState.getStackY()); + viewState.setYTranslation(viewState.getYTranslation() + stackTop); } @VisibleForTesting @@ -1002,8 +1024,11 @@ public class StackScrollAlgorithm { // Animate pinned HUN bottom corners to and from original roundness. final float originalCornerRadius = row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius); + final float stackTop = SceneContainerFlag.isEnabled() + ? ambientState.getStackTop() + : ambientState.getStackY(); final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(), - ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius); + stackTop, getMaxAllowedChildHeight(row), originalCornerRadius); row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO); row.addOnDetachResetRoundness(STACK_SCROLL_ALGO); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index eaaa9a1523c2..762c507220fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -50,8 +50,11 @@ interface NotificationScrollView { /** set the y position in px of the top of the stack in this view's coordinates */ fun setStackTop(stackTop: Float) - /** set the y position in px of the bottom of the stack in this view's coordinates */ - fun setStackBottom(stackBottom: Float) + /** + * set the bottom-most acceptable y-position of the bottom of the notification stack/ shelf / + * footer. + */ + fun setStackCutoff(stackBottom: Float) /** set the y position in px of the top of the HUN in this view's coordinates */ fun setHeadsUpTop(headsUpTop: Float) @@ -61,8 +64,10 @@ interface NotificationScrollView { /** Set a consumer for synthetic scroll events */ fun setSyntheticScrollConsumer(consumer: Consumer<Float>?) + /** Set a consumer for current gesture overscroll events */ fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?) + /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index cf5366b92cda..497ffcab32f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -154,6 +154,14 @@ constructor( } } + if (!SceneContainerFlag.isEnabled) { + launch { + // For when the entire view should fade, such as with the brightness + // slider + viewModel.panelAlpha.collect { controller.setMaxAlphaFromView(it) } + } + } + if (communalHub()) { launch { viewModel.glanceableHubAlpha.collect { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index a21db12db80b..ebb0d7dcee10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -90,8 +90,10 @@ constructor( 1f } else if ( shadeMode != ShadeMode.Split && - transitionState.fromScene in SceneFamilies.Home && - transitionState.toScene in quickSettingsScene + (transitionState.fromScene in SceneFamilies.Home && + transitionState.toScene == quickSettingsScene) || + (transitionState.fromScene == quickSettingsScene && + transitionState.toScene in SceneFamilies.Home) ) { // during QS expansion, increase fraction at same rate as scrim alpha, // but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 1fc2821e057d..8e58ffbb399e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -21,6 +21,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.annotation.VisibleForTesting import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager @@ -75,6 +76,7 @@ import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.FlowDumperImpl import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -136,6 +138,7 @@ constructor( private val primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel, private val aodBurnInViewModel: AodBurnInViewModel, + private val communalSceneInteractor: CommunalSceneInteractor, unfoldTransitionInteractor: UnfoldTransitionInteractor, ) : FlowDumperImpl(dumpManager) { private val statesForConstrainedNotifications: Set<KeyguardState> = @@ -470,6 +473,19 @@ constructor( } .dumpWhileCollecting("isTransitioningToHiddenKeyguard") + val panelAlpha = keyguardInteractor.panelAlpha + + private fun bouncerToGoneNotificationAlpha(viewState: ViewStateAccessor): Flow<Float> = + merge( + primaryBouncerToGoneTransitionViewModel.notificationAlpha, + alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState), + ) + .sample(communalSceneInteractor.isCommunalVisible) { alpha, isCommunalVisible -> + // when glanceable hub is visible, hide notifications during the transition to GONE + if (isCommunalVisible) 0f else alpha + } + .dumpWhileCollecting("bouncerToGoneNotificationAlpha") + fun keyguardAlpha(viewState: ViewStateAccessor): Flow<Float> { // All transition view models are mututally exclusive, and safe to merge val alphaTransitions = @@ -477,7 +493,7 @@ constructor( keyguardInteractor.dismissAlpha.dumpWhileCollecting( "keyguardInteractor.dismissAlpha" ), - alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState), + bouncerToGoneNotificationAlpha(viewState), aodToGoneTransitionViewModel.notificationAlpha(viewState), aodToLockscreenTransitionViewModel.notificationAlpha, aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), @@ -495,8 +511,9 @@ constructor( occludedToAodTransitionViewModel.lockscreenAlpha, occludedToGoneTransitionViewModel.notificationAlpha(viewState), occludedToLockscreenTransitionViewModel.lockscreenAlpha, - primaryBouncerToGoneTransitionViewModel.notificationAlpha, primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, + glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, + lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha, ) return merge( 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 201133288de5..91b5d0be6f04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -39,7 +39,7 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastController.CastDevice; +import com.android.systemui.statusbar.policy.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; import com.android.systemui.statusbar.policy.DeviceControlsController; @@ -430,8 +430,7 @@ public class AutoTileManager implements UserAwareController { boolean isCasting = false; for (CastDevice device : mCastController.getCastDevices()) { - if (device.state == CastDevice.STATE_CONNECTED - || device.state == CastDevice.STATE_CONNECTING) { + if (device.isCasting()) { isCasting = true; break; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 62c139b7cf31..5c262f3124a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1481,7 +1481,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return (v, event) -> { mAutoHideController.checkUserAutoHide(event); mRemoteInputManager.checkRemoteInputOutside(event); - if (!MigrateClocksToBlueprint.isEnabled()) { + if (!MigrateClocksToBlueprint.isEnabled() || mQsController.isCustomizing()) { + // For migrate clocks flag, when the user is editing QS tiles they need to be able + // to touch outside the customizer to close it, such as on the status or nav bar. mShadeController.onStatusBarTouch(event); } return getNotificationShadeWindowView().onTouchEvent(event); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 3925bebe55fd..c5dcb0933407 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -39,6 +39,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener; +import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository; @@ -86,7 +87,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>(); private final VisualStabilityProvider mVisualStabilityProvider; - private final AvalancheController mAvalancheController; + private AvalancheController mAvalancheController; // TODO(b/328393698) move the topHeadsUpRow logic to an interactor private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow = @@ -175,6 +176,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); } + mVisualStabilityProvider.addPersistentReorderingBannedListener(mOnReorderingBannedListener); } public void setAnimationStateHandler(AnimationStateHandler handler) { @@ -382,6 +384,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> { mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false); + mAvalancheController.setEnableAtRuntime(true); + for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) { if (isHeadsUpEntry(entry.getKey())) { // Maybe the heads-up was removed already @@ -392,6 +396,28 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true); }; + private final OnReorderingBannedListener mOnReorderingBannedListener = () -> { + if (mAvalancheController != null) { + // Waiting HUNs in AvalancheController are still promoted to the HUN section and thus + // seen in open shade; clear them so we don't show them again when the shade closes and + // reordering is allowed again. + mAvalancheController.logDroppedHuns(mAvalancheController.getWaitingKeys().size()); + mAvalancheController.clearNext(); + + // In open shade the first HUN is pinned, and visual stability logic prevents us from + // unpinning this first HUN as long as the shade remains open. AvalancheController only + // shows the next HUN when the currently showing HUN is unpinned, so we must disable + // throttling here so that the incoming HUN stream is not forever paused. This is reset + // when reorder becomes allowed. + mAvalancheController.setEnableAtRuntime(false); + + // Note that we cannot do the above when + // 1) the remove runnable runs because its delay means it may not run before shade close + // 2) reordering is allowed again (when shade closes) because the HUN appear animation + // will have started by then + } + }; + /////////////////////////////////////////////////////////////////////////////////////////////// // HeadsUpManager utility (protected) methods overrides: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index e400ab685d8e..639560fcdcd5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -247,9 +247,10 @@ constructor( val actuallyShowOverLockscreen = showOverLockscreen && intent.isActivity && - (skipLockscreenChecks || activityIntentHelper.wouldPendingShowOverLockscreen( - intent, - lockScreenUserManager.currentUserId + (skipLockscreenChecks || + activityIntentHelper.wouldPendingShowOverLockscreen( + intent, + lockScreenUserManager.currentUserId )) val animate = @@ -470,12 +471,16 @@ constructor( shadeControllerLazy.get().collapseShadeForActivityStart() } if (communalHub()) { - communalSceneInteractor.snapToSceneForActivityStart(CommunalScenes.Blank) + communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard() } return deferred } override fun willRunAnimationOnKeyguard(): Boolean { + if (communalHub() && communalSceneInteractor.isIdleOnCommunal.value) { + // Override to false when launching activity over the hub that requires auth + return false + } return willAnimateOnKeyguard } } @@ -557,7 +562,7 @@ constructor( override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { super.onTransitionAnimationStart(isExpandingFullyAbove) if (communalHub()) { - communalSceneInteractor.snapToSceneForActivityStart( + communalSceneInteractor.snapToScene( CommunalScenes.Blank, ActivityTransitionAnimator.TIMINGS.totalDuration ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 3784132415d1..3ba62b16a748 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -44,6 +44,7 @@ import android.view.View; import androidx.lifecycle.Observer; +import com.android.systemui.Flags; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; @@ -57,12 +58,13 @@ import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.res.R; import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.screenrecord.data.model.ScreenRecordModel; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.ui.StatusBarIconController; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastController.CastDevice; +import com.android.systemui.statusbar.policy.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -354,7 +356,11 @@ public class PhoneStatusBarPolicy mProvisionedController.addCallback(this); mCurrentUserSetup = mProvisionedController.isCurrentUserSetup(); mZenController.addCallback(this); - mCast.addCallback(mCastCallback); + if (!Flags.statusBarScreenSharingChips()) { + // If the flag is enabled, the cast icon is handled in the new screen sharing chips + // instead of here so we don't need to listen for events here. + mCast.addCallback(mCastCallback); + } mHotspot.addCallback(mHotspotCallback); mNextAlarmController.addCallback(mNextAlarmCallback); mDataSaver.addCallback(this); @@ -362,7 +368,11 @@ public class PhoneStatusBarPolicy mPrivacyItemController.addCallback(this); mSensorPrivacyController.addCallback(mSensorPrivacyListener); mLocationController.addCallback(this); - mRecordingController.addCallback(this); + if (!Flags.statusBarScreenSharingChips()) { + // If the flag is enabled, the screen record icon is handled in the new screen sharing + // chips instead of here so we don't need to listen for events here. + mRecordingController.addCallback(this); + } mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(), this::onConnectedDisplayAvailabilityChanged); @@ -519,10 +529,14 @@ public class PhoneStatusBarPolicy } private void updateCast() { + if (Flags.statusBarScreenSharingChips()) { + // The cast icon is handled in the new screen sharing chips instead of here. + return; + } + boolean isCasting = false; for (CastDevice device : mCast.getCastDevices()) { - if (device.state == CastDevice.STATE_CONNECTING - || device.state == CastDevice.STATE_CONNECTED) { + if (device.isCasting()) { isCasting = true; break; } @@ -789,6 +803,10 @@ public class PhoneStatusBarPolicy private Runnable mRemoveCastIconRunnable = new Runnable() { @Override public void run() { + if (Flags.statusBarScreenSharingChips()) { + // The cast icon is handled in the new screen sharing chips instead of here. + return; + } if (DEBUG) Log.v(TAG, "updateCast: hiding icon NOW"); mIconController.setIconVisibility(mSlotCast, false); } @@ -797,8 +815,13 @@ public class PhoneStatusBarPolicy // Screen Recording @Override public void onCountdown(long millisUntilFinished) { + if (Flags.statusBarScreenSharingChips()) { + // The screen record icon is handled in the new screen sharing chips instead of here. + return; + } if (DEBUG) Log.d(TAG, "screenrecord: countdown " + millisUntilFinished); - int countdown = (int) Math.floorDiv(millisUntilFinished + 500, 1000); + int countdown = + (int) ScreenRecordModel.Starting.Companion.toCountdownSeconds(millisUntilFinished); int resourceId = R.drawable.stat_sys_screen_record; String description = Integer.toString(countdown); switch (countdown) { @@ -821,6 +844,10 @@ public class PhoneStatusBarPolicy @Override public void onCountdownEnd() { + if (Flags.statusBarScreenSharingChips()) { + // The screen record icon is handled in the new screen sharing chips instead of here. + return; + } if (DEBUG) Log.d(TAG, "screenrecord: hiding icon during countdown"); mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false)); // Reset talkback priority @@ -830,6 +857,10 @@ public class PhoneStatusBarPolicy @Override public void onRecordingStart() { + if (Flags.statusBarScreenSharingChips()) { + // The screen record icon is handled in the new screen sharing chips instead of here. + return; + } if (DEBUG) Log.d(TAG, "screenrecord: showing icon"); mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, @@ -839,6 +870,10 @@ public class PhoneStatusBarPolicy @Override public void onRecordingEnd() { + if (Flags.statusBarScreenSharingChips()) { + // The screen record icon is handled in the new screen sharing chips instead of here. + return; + } // Ensure this is on the main thread if (DEBUG) Log.d(TAG, "screenrecord: hiding icon"); mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 4b1ee58c2cc6..02187844d6da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -68,7 +68,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mIsStatusBarExpanded = false; - private boolean mIsIdleOnGone = false; + private boolean mIsIdleOnGone = true; private boolean mShouldAdjustInsets = false; private View mNotificationShadeWindowView; private View mNotificationPanelView; @@ -282,7 +282,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { // since we don't want stray touches to go through the light reveal scrim to whatever is // underneath. return mIsStatusBarExpanded - || !mIsIdleOnGone + || (SceneContainerFlag.isEnabled() && !mIsIdleOnGone) || mPrimaryBouncerInteractor.isShowing().getValue() || mAlternateBouncerInteractor.isVisibleState() || mUnlockedScreenOffAnimationController.isAnimationPlaying(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt index ec3af87f2b9b..a7c5f78e5b69 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt @@ -345,32 +345,67 @@ constructor( orElse = flowOf(false), retrySignal = telephonyProcessCrashedEvent, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) private fun satelliteProvisioned(sm: SupportedSatelliteManager): Flow<Boolean> = conflatedCallbackFlow { - val callback = SatelliteProvisionStateCallback { provisioned -> - logBuffer.i { - "onSatelliteProvisionStateChanged: " + - if (provisioned) "provisioned" else "not provisioned" + // TODO(b/347992038): SatelliteManager should be sending the current provisioned + // status when we register a callback. Until then, we have to manually query here. + + // First, check to see what the current status is, and send the result to the output + trySend(queryIsSatelliteProvisioned(sm)) + + val callback = SatelliteProvisionStateCallback { provisioned -> + logBuffer.i { + "onSatelliteProvisionStateChanged: " + + if (provisioned) "provisioned" else "not provisioned" + } + trySend(provisioned) } - trySend(provisioned) - } - var registered = false - try { - sm.registerForProvisionStateChanged( - bgDispatcher.asExecutor(), - callback, - ) - registered = true - } catch (e: Exception) { - logBuffer.e("error registering for provisioning state callback", e) + var registered = false + try { + logBuffer.i { "registerForProvisionStateChanged" } + sm.registerForProvisionStateChanged( + bgDispatcher.asExecutor(), + callback, + ) + registered = true + } catch (e: Exception) { + logBuffer.e("error registering for provisioning state callback", e) + } + + awaitClose { + if (registered) { + sm.unregisterForProvisionStateChanged(callback) + } + } } + .flowOn(bgDispatcher) + + /** Check the current satellite provisioning status. */ + private suspend fun queryIsSatelliteProvisioned(sm: SupportedSatelliteManager): Boolean = + withContext(bgDispatcher) { + suspendCancellableCoroutine { continuation -> + val receiver = + object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> { + override fun onResult(result: Boolean) { + logBuffer.i { "requestIsProvisioned.onResult: $result" } + continuation.resume(result) + } - awaitClose { - if (registered) { - sm.unregisterForProvisionStateChanged(callback) + override fun onError(exception: SatelliteManager.SatelliteException) { + logBuffer.e("requestIsProvisioned.onError:", exception) + continuation.resume(false) + } + } + + logBuffer.i { "Query for current satellite provisioned state." } + try { + sm.requestIsProvisioned(bgDispatcher.asExecutor(), receiver) + } catch (e: Exception) { + logBuffer.e("Exception while calling SatelliteManager.requestIsProvisioned:", e) + continuation.resume(false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index 7644653306be..68983a1a10b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -18,8 +18,11 @@ package com.android.systemui.statusbar.pipeline.shared.ui.binder import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.content.res.ColorStateList +import android.graphics.drawable.GradientDrawable import android.view.View import android.widget.ImageView +import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.Flags @@ -29,6 +32,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.binder.ChipChronometerBinder import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.view.ChipChronometer import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel @@ -84,23 +88,41 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa if (Flags.statusBarScreenSharingChips()) { val chipView: View = view.requireViewById(R.id.ongoing_activity_chip) + val chipContext = chipView.context val chipIconView: ImageView = chipView.requireViewById(R.id.ongoing_activity_chip_icon) val chipTimeView: ChipChronometer = chipView.requireViewById(R.id.ongoing_activity_chip_time) + val chipTextView: TextView = + chipView.requireViewById(R.id.ongoing_activity_chip_text) + val chipBackgroundView = + chipView.requireViewById<ChipBackgroundContainer>( + R.id.ongoing_activity_chip_background + ) launch { viewModel.ongoingActivityChip.collect { chipModel -> when (chipModel) { is OngoingActivityChipModel.Shown -> { - IconViewBinder.bind(chipModel.icon, chipIconView) - ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView) + // Data + IconViewBinder.bindNullable(chipModel.icon, chipIconView) + setChipMainContent(chipModel, chipTextView, chipTimeView) chipView.setOnClickListener(chipModel.onClickListener) + // Colors + val textColor = chipModel.colors.text(chipContext) + chipIconView.imageTintList = ColorStateList.valueOf(textColor) + chipTimeView.setTextColor(textColor) + chipTextView.setTextColor(textColor) + (chipBackgroundView.background as GradientDrawable).color = + chipModel.colors.background(chipContext) + listener.onOngoingActivityStatusChanged( hasOngoingActivity = true ) } is OngoingActivityChipModel.Hidden -> { + // The Chronometer should be stopped to prevent leaks -- see + // b/192243808 and [Chronometer.start]. chipTimeView.stop() listener.onOngoingActivityStatusChanged( hasOngoingActivity = false @@ -114,6 +136,61 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } + private fun setChipMainContent( + chipModel: OngoingActivityChipModel.Shown, + chipTextView: TextView, + chipTimeView: ChipChronometer, + ) { + when (chipModel) { + is OngoingActivityChipModel.Shown.Countdown -> { + chipTextView.text = chipModel.secondsUntilStarted.toString() + chipTextView.visibility = View.VISIBLE + + // The Chronometer should be stopped to prevent leaks -- see b/192243808 and + // [Chronometer.start]. + chipTimeView.stop() + chipTimeView.visibility = View.GONE + } + is OngoingActivityChipModel.Shown.Timer -> { + ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView) + chipTimeView.visibility = View.VISIBLE + + chipTextView.visibility = View.GONE + } + } + updateChipTextPadding(chipModel, chipTextView, chipTimeView) + } + + private fun updateChipTextPadding( + chipModel: OngoingActivityChipModel.Shown, + chipTextView: TextView, + chipTimeView: ChipChronometer, + ) { + val requiresPadding = chipModel.icon != null + if (requiresPadding) { + chipTextView.addChipTextPaddingStart() + chipTimeView.addChipTextPaddingStart() + } else { + chipTextView.removeChipTextPaddingStart() + chipTimeView.removeChipTextPaddingStart() + } + } + + private fun View.addChipTextPaddingStart() { + this.setPaddingRelative( + this.context.resources.getDimensionPixelSize( + R.dimen.ongoing_activity_chip_icon_text_padding + ), + paddingTop, + paddingEnd, + paddingBottom, + ) + } + + private fun View.removeChipTextPaddingStart() { + this.setPaddingRelative(/* start= */ 0, paddingTop, paddingEnd, paddingBottom) + } + private fun animateLightsOutView(view: View, visible: Boolean) { view.animate().cancel() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index 32774e0b270a..dbe54f346072 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -39,6 +39,7 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) private val tag = "AvalancheController" private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG) + var enableAtRuntime = true // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null @@ -81,13 +82,17 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) dumpManager.registerNormalDumpable(tag, /* module */ this) } + fun isEnabled() : Boolean { + return NotificationThrottleHun.isEnabled && enableAtRuntime + } + fun getShowingHunKey(): String { return getKey(headsUpEntryShowing) } /** Run or delay Runnable for given HeadsUpEntry */ fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) { - if (!NotificationThrottleHun.isEnabled) { + if (!isEnabled()) { runnable.run() return } @@ -143,7 +148,7 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) * all Runnables associated with that entry. */ fun delete(entry: HeadsUpEntry?, runnable: Runnable, label: String) { - if (!NotificationThrottleHun.isEnabled) { + if (!isEnabled()) { runnable.run() return } @@ -184,7 +189,7 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration. */ fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int { - if (!NotificationThrottleHun.isEnabled) { + if (!isEnabled()) { // Use default duration, like we did before AvalancheController existed return autoDismissMs } @@ -233,7 +238,7 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) /** Return true if entry is waiting to show. */ fun isWaiting(key: String): Boolean { - if (!NotificationThrottleHun.isEnabled) { + if (!isEnabled()) { return false } for (entry in nextMap.keys) { @@ -246,7 +251,7 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) /** Return list of keys for huns waiting */ fun getWaitingKeys(): MutableList<String> { - if (!NotificationThrottleHun.isEnabled) { + if (!isEnabled()) { return mutableListOf() } val keyList = mutableListOf<String>() @@ -257,7 +262,7 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) } fun getWaitingEntry(key: String): HeadsUpEntry? { - if (!NotificationThrottleHun.isEnabled) { + if (!isEnabled()) { return null } for (headsUpEntry in nextMap.keys) { @@ -269,7 +274,7 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) } fun getWaitingEntryList(): List<HeadsUpEntry> { - if (!NotificationThrottleHun.isEnabled) { + if (!isEnabled()) { return mutableListOf() } return nextMap.keys.toList() @@ -310,11 +315,7 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) // Remove runnable labels for dropped huns val listToDrop = nextList.subList(1, nextList.size) - - // Log dropped HUNs - for (e in listToDrop) { - uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED) - } + logDroppedHuns(listToDrop.size) if (debug) { // Clear runnable labels @@ -331,6 +332,12 @@ constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList) } + fun logDroppedHuns(numDropped: Int) { + for (n in 1..numDropped) { + uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED) + } + } + fun clearNext() { nextList.clear() nextMap.clear() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java index abedd3220c93..a3dcc3b6f851 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java @@ -37,15 +37,4 @@ public interface CastController extends CallbackController<Callback>, Dumpable { void onCastDevicesChanged(); } - public static final class CastDevice { - public static final int STATE_DISCONNECTED = 0; - public static final int STATE_CONNECTING = 1; - public static final int STATE_CONNECTED = 2; - - public String id; - public String name; - public String description; - public int state = STATE_DISCONNECTED; - public Object tag; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java index 64bdf605e13d..45cb52a8b1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java @@ -19,15 +19,12 @@ package com.android.systemui.statusbar.policy; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Handler; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -37,8 +34,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.annotations.GuardedBy; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.res.R; -import com.android.systemui.util.Utils; import java.io.PrintWriter; import java.util.ArrayList; @@ -51,10 +46,11 @@ import javax.inject.Inject; /** Platform implementation of the cast controller. **/ @SysUISingleton public class CastControllerImpl implements CastController { - private static final String TAG = "CastController"; + public static final String TAG = "CastController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; + private final PackageManager mPackageManager; @GuardedBy("mCallbacks") private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final MediaRouter mMediaRouter; @@ -68,8 +64,12 @@ public class CastControllerImpl implements CastController { private MediaProjectionInfo mProjection; @Inject - public CastControllerImpl(Context context, DumpManager dumpManager) { + public CastControllerImpl( + Context context, + PackageManager packageManager, + DumpManager dumpManager) { mContext = context; + mPackageManager = packageManager; mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mMediaRouter.setRouterGroupId(MediaRouter.MIRRORING_GROUP_ID); mProjectionManager = (MediaProjectionManager) @@ -156,36 +156,17 @@ public class CastControllerImpl implements CastController { final ArrayList<CastDevice> devices = new ArrayList<>(); synchronized(mRoutes) { for (RouteInfo route : mRoutes.values()) { - final CastDevice device = new CastDevice(); - device.id = route.getTag().toString(); - final CharSequence name = route.getName(mContext); - device.name = name != null ? name.toString() : null; - final CharSequence description = route.getDescription(); - device.description = description != null ? description.toString() : null; - - int statusCode = route.getStatusCode(); - if (statusCode == RouteInfo.STATUS_CONNECTING) { - device.state = CastDevice.STATE_CONNECTING; - } else if (route.isSelected() || statusCode == RouteInfo.STATUS_CONNECTED) { - device.state = CastDevice.STATE_CONNECTED; - } else { - device.state = CastDevice.STATE_DISCONNECTED; - } - - device.tag = route; - devices.add(device); + devices.add(CastDevice.Companion.toCastDevice(route, mContext)); } } synchronized (mProjectionLock) { if (mProjection != null) { - final CastDevice device = new CastDevice(); - device.id = mProjection.getPackageName(); - device.name = getAppName(mProjection.getPackageName()); - device.description = mContext.getString(R.string.quick_settings_casting); - device.state = CastDevice.STATE_CONNECTED; - device.tag = mProjection; - devices.add(device); + devices.add( + CastDevice.Companion.toCastDevice( + mProjection, + mContext, + mPackageManager)); } } @@ -194,18 +175,18 @@ public class CastControllerImpl implements CastController { @Override public void startCasting(CastDevice device) { - if (device == null || device.tag == null) return; - final RouteInfo route = (RouteInfo) device.tag; + if (device == null || device.getTag() == null) return; + final RouteInfo route = (RouteInfo) device.getTag(); if (DEBUG) Log.d(TAG, "startCasting: " + routeToString(route)); mMediaRouter.selectRoute(ROUTE_TYPE_REMOTE_DISPLAY, route); } @Override public void stopCasting(CastDevice device) { - final boolean isProjection = device.tag instanceof MediaProjectionInfo; + final boolean isProjection = device.getTag() instanceof MediaProjectionInfo; if (DEBUG) Log.d(TAG, "stopCasting isProjection=" + isProjection); if (isProjection) { - final MediaProjectionInfo projection = (MediaProjectionInfo) device.tag; + final MediaProjectionInfo projection = (MediaProjectionInfo) device.getTag(); if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) { mProjectionManager.stopActiveProjection(); } else { @@ -219,7 +200,7 @@ public class CastControllerImpl implements CastController { @Override public boolean hasConnectedCastDevice() { return getCastDevices().stream().anyMatch( - castDevice -> castDevice.state == CastDevice.STATE_CONNECTED); + castDevice -> castDevice.getState() == CastDevice.CastState.Connected); } private void setProjection(MediaProjectionInfo projection, boolean started) { @@ -241,27 +222,6 @@ public class CastControllerImpl implements CastController { } } - private String getAppName(String packageName) { - final PackageManager pm = mContext.getPackageManager(); - if (Utils.isHeadlessRemoteDisplayProvider(pm, packageName)) { - return ""; - } - - try { - final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0); - if (appInfo != null) { - final CharSequence label = appInfo.loadLabel(pm); - if (!TextUtils.isEmpty(label)) { - return label.toString(); - } - } - Log.w(TAG, "No label found for package: " + packageName); - } catch (NameNotFoundException e) { - Log.w(TAG, "Error getting appName for package: " + packageName, e); - } - return packageName; - } - private void updateRemoteDisplays() { synchronized(mRoutes) { mRoutes.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt new file mode 100644 index 000000000000..5fc160bd5da3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy + +import android.content.Context +import android.content.pm.PackageManager +import android.media.MediaRouter +import android.media.projection.MediaProjectionInfo +import android.text.TextUtils +import android.util.Log +import com.android.systemui.res.R +import com.android.systemui.util.Utils + +/** Represents a specific cast session. */ +data class CastDevice( + val id: String, + /** A human-readable name of what is receiving the cast (e.g. "Home Speaker", "Abc App"). */ + val name: String?, + /** An optional description with more information about the cast. */ + val description: String? = null, + val state: CastState, + val origin: CastOrigin, + /** Optional tag to use as a comparison value between cast sessions. */ + val tag: Any? = null, +) { + val isCasting = state == CastState.Connecting || state == CastState.Connected + + companion object { + /** Creates a [CastDevice] based on the provided information from MediaRouter. */ + fun MediaRouter.RouteInfo.toCastDevice(context: Context): CastDevice { + val state = + when { + statusCode == MediaRouter.RouteInfo.STATUS_CONNECTING -> CastState.Connecting + this.isSelected || statusCode == MediaRouter.RouteInfo.STATUS_CONNECTED -> + CastState.Connected + else -> CastState.Disconnected + } + return CastDevice( + id = this.tag.toString(), + name = this.getName(context)?.toString(), + description = this.description?.toString(), + state = state, + tag = this, + origin = CastOrigin.MediaRouter, + ) + } + + /** Creates a [CastDevice] based on the provided information from MediaProjection. */ + fun MediaProjectionInfo.toCastDevice( + context: Context, + packageManager: PackageManager + ): CastDevice { + return CastDevice( + id = this.packageName, + name = getAppName(this.packageName, packageManager), + description = context.getString(R.string.quick_settings_casting), + state = CastState.Connected, + tag = this, + origin = CastOrigin.MediaProjection, + ) + } + + private fun getAppName(packageName: String, packageManager: PackageManager): String { + if (Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)) { + return "" + } + try { + val appInfo = packageManager.getApplicationInfo(packageName, 0) + val label = appInfo.loadLabel(packageManager) + if (!TextUtils.isEmpty(label)) { + return label.toString() + } + Log.w(CastControllerImpl.TAG, "No label found for package: $packageName") + } catch (e: PackageManager.NameNotFoundException) { + Log.w(CastControllerImpl.TAG, "Error getting appName for package: $packageName", e) + } + return packageName + } + } + + enum class CastState { + Disconnected, + Connecting, + Connected, + } + + enum class CastOrigin { + /** SysUI found out about this cast device from MediaRouter APIs. */ + MediaRouter, + /** SysUI found out about this cast device from MediaProjection APIs. */ + MediaProjection, + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java index de0eb493c83d..528ef491f40c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java @@ -31,8 +31,8 @@ import com.android.systemui.util.Assert; import kotlin.Unit; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -42,7 +42,13 @@ import javax.inject.Inject; public class DevicePostureControllerImpl implements DevicePostureController { /** From androidx.window.common.COMMON_STATE_USE_BASE_STATE */ private static final int COMMON_STATE_USE_BASE_STATE = 1000; - private final List<Callback> mListeners = new ArrayList<>(); + /** + * Despite this is always used only from the main thread, it might be that some listener + * unregisters itself while we're sending the update, ending up modifying this while we're + * iterating it. + * Keeping a threadsafe list of listeners helps preventing ConcurrentModificationExceptions. + */ + private final List<Callback> mListeners = new CopyOnWriteArrayList<>(); private final List<DeviceState> mSupportedStates; private DeviceState mCurrentDeviceState; private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java index ad19729aa10e..23e40b220a68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java @@ -24,7 +24,14 @@ import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; import com.android.systemui.statusbar.policy.ZenModeController.Callback; +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor; +/** + * Callback-based controller for listening to (or making) zen mode changes. Please prefer using the + * Flow-based {@link ZenModeInteractor} for new code instead of this. + * + * TODO(b/308591859): This should eventually be replaced by ZenModeInteractor/ZenModeRepository. + */ public interface ZenModeController extends CallbackController<Callback> { void setZen(int zen, Uri conditionId, String reason); int getZen(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 15200bd0ac54..e08e4d70a18e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -73,7 +73,6 @@ import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl; import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepositoryModule; -import com.android.systemui.statusbar.policy.data.repository.ZenModeRepositoryModule; import dagger.Binds; import dagger.Module; @@ -84,7 +83,7 @@ import java.util.concurrent.Executor; import javax.inject.Named; /** Dagger Module for code in the statusbar.policy package. */ -@Module(includes = { DeviceProvisioningRepositoryModule.class, ZenModeRepositoryModule.class }) +@Module(includes = {DeviceProvisioningRepositoryModule.class}) public interface StatusBarPolicyModule { String DEVICE_STATE_ROTATION_LOCK_DEFAULTS = "DEVICE_STATE_ROTATION_LOCK_DEFAULTS"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt deleted file mode 100644 index 94ab58ae5a3d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.policy.data.repository - -import android.app.NotificationManager -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow -import com.android.systemui.statusbar.policy.ZenModeController -import dagger.Binds -import dagger.Module -import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow - -/** - * A repository that holds information about the status and configuration of Zen Mode (or Do Not - * Disturb/DND Mode). - */ -interface ZenModeRepository { - val zenMode: Flow<Int> - val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> -} - -class ZenModeRepositoryImpl -@Inject -constructor( - private val zenModeController: ZenModeController, -) : ZenModeRepository { - // TODO(b/308591859): ZenModeController should use flows instead of callbacks. The - // conflatedCallbackFlows here should be replaced eventually, see: - // https://docs.google.com/document/d/1gAiuYupwUAFdbxkDXa29A4aFNu7XoCd7sCIk31WTnHU/edit?resourcekey=0-J4ZBiUhLhhQnNobAcI2vIw - - override val zenMode: Flow<Int> = conflatedCallbackFlow { - val callback = - object : ZenModeController.Callback { - override fun onZenChanged(zen: Int) { - trySend(zen) - } - } - zenModeController.addCallback(callback) - trySend(zenModeController.zen) - - awaitClose { zenModeController.removeCallback(callback) } - } - - override val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> = - conflatedCallbackFlow { - val callback = - object : ZenModeController.Callback { - override fun onConsolidatedPolicyChanged(policy: NotificationManager.Policy?) { - trySend(policy) - } - } - zenModeController.addCallback(callback) - trySend(zenModeController.consolidatedPolicy) - - awaitClose { zenModeController.removeCallback(callback) } - } -} - -@Module -interface ZenModeRepositoryModule { - @Binds fun bindImpl(impl: ZenModeRepositoryImpl): ZenModeRepository -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt index ae31851cb8f5..f5d7d0029e90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.policy.domain.interactor import android.provider.Settings -import com.android.systemui.statusbar.policy.data.repository.ZenModeRepository +import com.android.settingslib.statusbar.notification.data.repository.ZenModeRepository import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -30,9 +30,9 @@ import kotlinx.coroutines.flow.map */ class ZenModeInteractor @Inject constructor(repository: ZenModeRepository) { val isZenModeEnabled: Flow<Boolean> = - repository.zenMode + repository.globalZenMode .map { - when (it) { + when (it ?: Settings.Global.ZEN_MODE_OFF) { Settings.Global.ZEN_MODE_ALARMS -> true Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> true Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> true diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt new file mode 100644 index 000000000000..b6c2ae794da9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.touchpad.tutorial.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.model.SysUiState +import com.android.systemui.settings.DisplayTracker +import com.android.systemui.shared.system.QuickStepContract +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@SysUISingleton +class TouchpadGesturesInteractor +@Inject +constructor( + private val sysUiState: SysUiState, + private val displayTracker: DisplayTracker, + @Background private val backgroundScope: CoroutineScope +) { + fun disableGestures() { + setGesturesState(disabled = true) + } + + fun enableGestures() { + setGesturesState(disabled = false) + } + + private fun setGesturesState(disabled: Boolean) { + backgroundScope.launch { + sysUiState + .setFlag(QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED, disabled) + .commitUpdate(displayTracker.defaultDisplayId) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/TouchpadTutorialViewModel.kt index 11740a8a4ecc..9e6553a4b234 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/TouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/TouchpadTutorialViewModel.kt @@ -18,11 +18,13 @@ package com.android.systemui.touchpad.tutorial.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject -class TouchpadTutorialViewModel : ViewModel() { +class TouchpadTutorialViewModel(private val gesturesInteractor: TouchpadGesturesInteractor) : + ViewModel() { private val _screen = MutableStateFlow(Screen.TUTORIAL_SELECTION) val screen: StateFlow<Screen> = _screen @@ -31,11 +33,20 @@ class TouchpadTutorialViewModel : ViewModel() { _screen.value = screen } - class Factory @Inject constructor() : ViewModelProvider.Factory { + fun onOpened() { + gesturesInteractor.disableGestures() + } + + fun onClosed() { + gesturesInteractor.enableGestures() + } + + class Factory @Inject constructor(private val gesturesInteractor: TouchpadGesturesInteractor) : + ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { - return TouchpadTutorialViewModel() as T + return TouchpadTutorialViewModel(gesturesInteractor) as T } } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/BackGestureTutorialScreen.kt new file mode 100644 index 000000000000..2460761c8592 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/BackGestureTutorialScreen.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.touchpad.tutorial.ui.view + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.android.systemui.res.R + +@Composable +fun BackGestureTutorialScreen( + onDoneButtonClicked: () -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier, +) { + BackHandler { onBack() } + Column( + verticalArrangement = Arrangement.Center, + modifier = + modifier + .background(color = MaterialTheme.colorScheme.surfaceContainer) + .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp) + .fillMaxSize() + ) { + Row(modifier = Modifier.fillMaxWidth().weight(1f)) { + TutorialDescription(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.width(76.dp)) + TutorialAnimation(modifier = Modifier.weight(1f).padding(top = 24.dp)) + } + DoneButton(onDoneButtonClicked = onDoneButtonClicked) + } +} + +@Composable +fun TutorialDescription(modifier: Modifier = Modifier) { + Column(verticalArrangement = Arrangement.Top, modifier = modifier) { + Text( + text = stringResource(id = R.string.touchpad_back_gesture_action_title), + style = MaterialTheme.typography.displayLarge + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = R.string.touchpad_back_gesture_guidance), + style = MaterialTheme.typography.bodyLarge + ) + } +} + +@Composable +fun TutorialAnimation(modifier: Modifier = Modifier) { + // below are just placeholder images, will be substituted by animations soon + Column(modifier = modifier.fillMaxWidth()) { + Image( + painter = painterResource(id = R.drawable.placeholder_touchpad_tablet_back_gesture), + contentDescription = + stringResource( + id = R.string.touchpad_back_gesture_screen_animation_content_description + ), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(24.dp)) + Image( + painter = painterResource(id = R.drawable.placeholder_touchpad_back_gesture), + contentDescription = + stringResource(id = R.string.touchpad_back_gesture_animation_content_description), + modifier = Modifier.fillMaxWidth() + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index b7629c7160ee..0c5c1874db8a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -20,14 +20,13 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.Lifecycle.State.STARTED -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.android.compose.theme.PlatformTheme -import com.android.systemui.touchpad.tutorial.ui.BackGestureTutorialViewModel import com.android.systemui.touchpad.tutorial.ui.GestureViewModelFactory import com.android.systemui.touchpad.tutorial.ui.HomeGestureTutorialViewModel import com.android.systemui.touchpad.tutorial.ui.Screen.BACK_GESTURE @@ -42,18 +41,27 @@ constructor( private val viewModelFactory: TouchpadTutorialViewModel.Factory, ) : ComponentActivity() { + private val vm by viewModels<TouchpadTutorialViewModel>(factoryProducer = { viewModelFactory }) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - setContent { - PlatformTheme { TouchpadTutorialScreen(viewModelFactory, closeTutorial = { finish() }) } - } + setContent { PlatformTheme { TouchpadTutorialScreen(vm) { finish() } } } + } + + override fun onResume() { + super.onResume() + vm.onOpened() + } + + override fun onPause() { + super.onPause() + vm.onClosed() } } @Composable -fun TouchpadTutorialScreen(viewModelFactory: ViewModelProvider.Factory, closeTutorial: () -> Unit) { - val vm = viewModel<TouchpadTutorialViewModel>(factory = viewModelFactory) +fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> Unit) { val activeScreen by vm.screen.collectAsStateWithLifecycle(STARTED) when (activeScreen) { TUTORIAL_SELECTION -> @@ -63,17 +71,16 @@ fun TouchpadTutorialScreen(viewModelFactory: ViewModelProvider.Factory, closeTut onActionKeyTutorialClicked = {}, onDoneButtonClicked = closeTutorial ) - BACK_GESTURE -> BackGestureTutorialScreen() + BACK_GESTURE -> + BackGestureTutorialScreen( + onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, + onBack = { vm.goTo(TUTORIAL_SELECTION) } + ) HOME_GESTURE -> HomeGestureTutorialScreen() } } @Composable -fun BackGestureTutorialScreen() { - val vm = viewModel<BackGestureTutorialViewModel>(factory = GestureViewModelFactory()) -} - -@Composable fun HomeGestureTutorialScreen() { val vm = viewModel<HomeGestureTutorialViewModel>(factory = GestureViewModelFactory()) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialComponents.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialComponents.kt new file mode 100644 index 000000000000..16fa91d59ea5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialComponents.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.touchpad.tutorial.ui.view + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.android.systemui.res.R + +@Composable +fun DoneButton(onDoneButtonClicked: () -> Unit, modifier: Modifier = Modifier) { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.fillMaxWidth() + ) { + Button(onClick = onDoneButtonClicked) { + Text(stringResource(R.string.touchpad_tutorial_done_button)) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialSelectionScreen.kt index 532eb1b82197..877bbe18fc43 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialSelectionScreen.kt @@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button @@ -114,16 +113,3 @@ private fun TutorialButton( Text(text = text, style = MaterialTheme.typography.headlineLarge) } } - -@Composable -private fun DoneButton(onDoneButtonClicked: () -> Unit, modifier: Modifier = Modifier) { - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - modifier = modifier.fillMaxWidth() - ) { - Button(onClick = onDoneButtonClicked) { - Text(stringResource(R.string.touchpad_tutorial_done_button)) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt index aea739dcb6cc..c5e98a1f1c02 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt @@ -22,9 +22,12 @@ import android.animation.ValueAnimator import android.annotation.BinderThread import android.os.SystemProperties import android.util.Log +import android.view.View import android.view.animation.DecelerateInterpolator import com.android.app.tracing.TraceUtils.traceAsync import com.android.internal.foldables.FoldLockSettingAvailabilityProvider +import com.android.internal.jank.Cuj.CUJ_FOLD_ANIM +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.power.domain.interactor.PowerInteractor @@ -65,7 +68,8 @@ constructor( @Background private val applicationScope: CoroutineScope, private val animationStatusRepository: AnimationStatusRepository, private val controllerFactory: FullscreenLightRevealAnimationController.Factory, - private val foldLockSettingAvailabilityProvider: FoldLockSettingAvailabilityProvider + private val foldLockSettingAvailabilityProvider: FoldLockSettingAvailabilityProvider, + private val interactionJankMonitor: InteractionJankMonitor ) : FullscreenLightRevealAnimation { private val revealProgressValueAnimator: ValueAnimator = @@ -87,7 +91,7 @@ constructor( controllerFactory.create( displaySelector = { minByOrNull { it.naturalWidth } }, effectFactory = { LinearSideLightRevealEffect(it.isVerticalRotation()) }, - overlayContainerName = SURFACE_CONTAINER_NAME + overlayContainerName = OVERLAY_TITLE ) controller.init() @@ -149,13 +153,23 @@ constructor( private suspend fun waitForGoToSleep() = traceAsync(TAG, "waitForGoToSleep()") { powerInteractor.isAsleep.filter { it }.first() } - private suspend fun playFoldLightRevealOverlayAnimation() { - revealProgressValueAnimator.duration = ANIMATION_DURATION - revealProgressValueAnimator.interpolator = DecelerateInterpolator() - revealProgressValueAnimator.addUpdateListener { animation -> - controller.updateRevealAmount(animation.animatedFraction) + private suspend fun playFoldLightRevealOverlayAnimation() = + trackCuj(CUJ_FOLD_ANIM, controller.scrimView) { + revealProgressValueAnimator.duration = ANIMATION_DURATION + revealProgressValueAnimator.interpolator = DecelerateInterpolator() + revealProgressValueAnimator.addUpdateListener { animation -> + controller.updateRevealAmount(animation.animatedFraction) + } + revealProgressValueAnimator.startAndAwaitCompletion() + } + + private suspend fun trackCuj(cuj: Int, view: View?, block: suspend () -> Unit) { + view?.let { interactionJankMonitor.begin(it, cuj) } + try { + block() + } finally { + if (view != null) interactionJankMonitor.end(cuj) } - revealProgressValueAnimator.startAndAwaitCompletion() } private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit = @@ -182,7 +196,7 @@ constructor( private companion object { const val TAG = "FoldLightRevealOverlayAnimation" const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L - const val SURFACE_CONTAINER_NAME = "fold-overlay-container" + const val OVERLAY_TITLE = "fold-animation-overlay" val ANIMATION_DURATION: Long get() = SystemProperties.getLong("persist.fold_animation_duration", 200L) } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt index 135edfcb6a42..a92137766c49 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt @@ -73,7 +73,7 @@ constructor( @Main private val executor: Executor, @Assisted private val displaySelector: List<DisplayInfo>.() -> DisplayInfo?, @Assisted private val lightRevealEffectFactory: (rotation: Int) -> LightRevealEffect, - @Assisted private val overlayContainerName: String + @Assisted private val overlayTitle: String ) { private lateinit var bgExecutor: Executor @@ -81,7 +81,10 @@ constructor( private var currentRotation: Int = context.display.rotation private var root: SurfaceControlViewHost? = null - private var scrimView: LightRevealScrim? = null + + /** The scrim view that is used to reveal the screen. */ + var scrimView: LightRevealScrim? = null + private set private val rotationWatcher = RotationWatcher() private val internalDisplayInfos: List<DisplayInfo> = @@ -153,7 +156,7 @@ constructor( val containerBuilder = SurfaceControl.Builder(SurfaceSession()) .setContainerLayer() - .setName(overlayContainerName) + .setName("FoldUnfoldAnimationContainer") displayAreaHelper .get() @@ -221,7 +224,7 @@ constructor( } format = PixelFormat.TRANSLUCENT type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY - title = javaClass.simpleName + title = overlayTitle layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS fitInsetsTypes = 0 diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index f355dd84e3cb..666e75f20b7f 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -71,7 +71,7 @@ constructor( fullscreenLightRevealAnimationControllerFactory.create( displaySelector = { maxByOrNull { it.naturalWidth } }, effectFactory = { LinearLightRevealEffect(it.isVerticalRotation()) }, - overlayContainerName = SURFACE_CONTAINER_NAME, + overlayContainerName = OVERLAY_TITLE, ) controller.init() bgExecutor = threadFactory.buildDelayableExecutorOnHandler(unfoldProgressHandler) @@ -194,7 +194,7 @@ constructor( private companion object { const val TAG = "UnfoldLightRevealOverlayAnimation" - const val SURFACE_CONTAINER_NAME = "unfold-overlay-container" + const val OVERLAY_TITLE = "unfold-animation-overlay" const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt index 7f1faeeb381d..cfcd6b14010d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt @@ -16,15 +16,23 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor +import android.bluetooth.BluetoothProfile import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo +import android.media.AudioManager +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothProfile +import com.android.settingslib.flags.Flags import com.android.settingslib.media.domain.interactor.SpatializerInteractor +import com.android.settingslib.volume.data.repository.AudioRepository +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.domain.interactor.AudioOutputInteractor import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted @@ -33,6 +41,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext /** * Provides an ability to access and update spatial audio and head tracking state. @@ -46,6 +55,8 @@ class SpatialAudioComponentInteractor constructor( audioOutputInteractor: AudioOutputInteractor, private val spatializerInteractor: SpatializerInteractor, + private val audioRepository: AudioRepository, + @Background private val backgroundCoroutineContext: CoroutineContext, @VolumePanelScope private val coroutineScope: CoroutineScope, ) { @@ -138,42 +149,85 @@ constructor( } private suspend fun AudioOutputDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? { - when (this) { - is AudioOutputDevice.BuiltIn -> return builtinSpeaker + return when (this) { + is AudioOutputDevice.BuiltIn -> builtinSpeaker is AudioOutputDevice.Bluetooth -> { - return listOf( - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_HEADSET, - cachedBluetoothDevice.address, - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_SPEAKER, - cachedBluetoothDevice.address, - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_BROADCAST, - cachedBluetoothDevice.address, - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - cachedBluetoothDevice.address, - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_HEARING_AID, - cachedBluetoothDevice.address, + if (Flags.enableDeterminingSpatialAudioAttributesByProfile()) { + getAudioDeviceAttributesByBluetoothProfile(cachedBluetoothDevice) + } else { + listOf( + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + cachedBluetoothDevice.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + cachedBluetoothDevice.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_BROADCAST, + cachedBluetoothDevice.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + cachedBluetoothDevice.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + cachedBluetoothDevice.address, + ) ) - ) - .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) } + .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) } + } } - else -> return null + else -> null } } + private suspend fun getAudioDeviceAttributesByBluetoothProfile( + cachedBluetoothDevice: CachedBluetoothDevice + ): AudioDeviceAttributes? = + withContext(backgroundCoroutineContext) { + cachedBluetoothDevice.profiles + .firstOrNull { + it.profileId in audioProfiles && it.isEnabled(cachedBluetoothDevice.device) + } + ?.let { profile: LocalBluetoothProfile -> + when (profile.profileId) { + BluetoothProfile.A2DP -> { + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP + } + BluetoothProfile.LE_AUDIO -> { + when ( + audioRepository.getBluetoothAudioDeviceCategory( + cachedBluetoothDevice.address + ) + ) { + AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER -> + AudioDeviceInfo.TYPE_BLE_SPEAKER + else -> AudioDeviceInfo.TYPE_BLE_HEADSET + } + } + BluetoothProfile.HEARING_AID -> { + AudioDeviceInfo.TYPE_HEARING_AID + } + else -> null + } + } + ?.let { + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + it, + cachedBluetoothDevice.address, + ) + } + } + private companion object { val builtinSpeaker = AudioDeviceAttributes( @@ -181,5 +235,7 @@ constructor( AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "" ) + val audioProfiles = + setOf(BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID) } } diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt index 2d84fbafcf6c..5b3f08f7bcac 100644 --- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt +++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt @@ -15,8 +15,8 @@ */ package androidx.core.animation -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.doOnEnd @@ -29,7 +29,7 @@ import org.junit.runner.RunWith * This test class validates that two tests' animators are isolated from each other when using the * same animator test rule. This is a test to prevent future instances of b/275602127. */ -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @RunWithLooper class AnimatorTestRuleIsolationTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java index 52af8fb62943..6ee8ffd91ab0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java @@ -18,10 +18,10 @@ package com.android.keyguard; import static com.google.common.truth.Truth.assertThat; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -32,7 +32,7 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class AuthKeyguardMessageAreaTest extends SysuiTestCase { private KeyguardMessageArea mKeyguardMessageArea; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt index ba46a874c260..049d77a7a454 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt @@ -17,9 +17,9 @@ package com.android.keyguard import android.content.Context -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.AttributeSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -31,7 +31,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class BouncerKeyguardMessageAreaTest : SysuiTestCase() { class FakeBouncerKeyguardMessageArea(context: Context, attrs: AttributeSet?) : diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt index 6512e70c51e1..bb2340aafbb5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt @@ -16,15 +16,15 @@ package com.android.keyguard +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class BouncerPanelExpansionCalculatorTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index 0a3225eecbe4..a8ab922a90b9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -50,9 +50,9 @@ import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; import android.text.TextUtils; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.logging.CarrierTextManagerLogger; @@ -84,7 +84,7 @@ import java.util.HashMap; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class CarrierTextManagerTest extends SysuiTestCase { private static final CharSequence SEPARATOR = " \u2014 "; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 69a6f2aed4c4..79933ee3b018 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -16,10 +16,10 @@ package com.android.keyguard import android.content.BroadcastReceiver -import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewTreeObserver import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -76,7 +76,7 @@ import org.mockito.junit.MockitoJUnit import com.android.systemui.Flags as AConfigFlags import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ClockEventControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt index 9a95b17fc7d1..347605de6616 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt @@ -21,8 +21,8 @@ import android.content.pm.PackageManager import android.os.PowerManager import android.telecom.TelecomManager import android.telephony.TelephonyManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.widget.LockPatternUtils @@ -45,7 +45,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class EmergencyButtonControllerTest : SysuiTestCase() { @Mock lateinit var emergencyButton: EmergencyButton diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index f9fe5e7e2b62..e724c60bfc54 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -28,10 +28,10 @@ import static org.mockito.Mockito.when; import android.os.SystemClock; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.KeyEvent; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.LatencyTracker; @@ -54,7 +54,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt index 634dac16d1ec..d170e4840842 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt @@ -17,7 +17,7 @@ package com.android.keyguard import android.hardware.biometrics.BiometricSourceType -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.UiEventLogger @@ -38,7 +38,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index cfa74f0ef432..1d7816848cd0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -30,9 +30,9 @@ import static org.mockito.Mockito.when; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.Flags; @@ -47,7 +47,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.verification.VerificationMode; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchControllerBaseTest { @Test public void testInit_viewAlreadyAttached() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt index 9a1a4e294e05..c2c0f5713d9b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt @@ -16,8 +16,8 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED import kotlinx.coroutines.Dispatchers @@ -26,7 +26,7 @@ import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardClockSwitchControllerWithCoroutinesTest : KeyguardClockSwitchControllerBaseTest() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index e2063d2f8a04..83443bee744e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -30,7 +30,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -39,6 +38,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.Flags; @@ -55,7 +55,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) // Need to run on the main thread because KeyguardSliceView$Row init checks for // the main thread before acquiring a wake lock. This class is constructed when // the keyguard_clock_switch layout is inflated. diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java index cc36cfa6a30a..dd58ea7db2bc 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java @@ -28,11 +28,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.hardware.display.DisplayManagerGlobal; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Display; import android.view.DisplayInfo; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -49,7 +49,7 @@ import org.mockito.MockitoAnnotations; import java.util.concurrent.Executor; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardDisplayManagerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index 6228ff837340..bd811814eb24 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -27,11 +27,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.hardware.biometrics.BiometricSourceType; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.Editable; import android.text.TextWatcher; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -47,7 +47,7 @@ import org.mockito.MockitoAnnotations; @SmallTest @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class KeyguardMessageAreaControllerTest extends SysuiTestCase { @Mock private ConfigurationController mConfigurationController; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java index c566826fdf19..8b5372a1f035 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java @@ -23,11 +23,11 @@ import static org.mockito.Mockito.when; import android.content.pm.PackageManager; import android.os.Handler; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -47,7 +47,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) public class KeyguardSliceViewControllerTest extends SysuiTestCase { @Mock diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java index a0e80653792d..d96518abc007 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java @@ -17,7 +17,6 @@ package com.android.keyguard; import android.graphics.Color; import android.net.Uri; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; @@ -26,6 +25,7 @@ import androidx.slice.SliceProvider; import androidx.slice.SliceSpecs; import androidx.slice.builders.ListBuilder; import androidx.slice.widget.RowContent; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -44,7 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @SmallTest @RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class KeyguardSliceViewTest extends SysuiTestCase { private KeyguardSliceView mKeyguardSliceView; private Uri mSliceUri; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt index ed61ee12849f..64e499674d9f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt @@ -1,7 +1,7 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Assert.assertEquals @@ -10,7 +10,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class KeyguardStatusAreaViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt index 3b57d8f85d83..c29439d89753 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt @@ -16,8 +16,8 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.power.shared.model.ScreenPowerState import kotlinx.coroutines.cancelChildren @@ -29,7 +29,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class KeyguardStatusViewControllerWithCoroutinesTest : KeyguardStatusViewControllerBaseTest() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt index afd2034424ff..16d2f0205c84 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt @@ -1,7 +1,7 @@ package com.android.keyguard +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.view.View @@ -15,7 +15,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class KeyguardStatusViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt index 336183d71ff1..2e41246a62a1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt @@ -16,8 +16,9 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.view.View +import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.ui.view.KeyguardRootView @@ -43,7 +44,7 @@ import org.mockito.kotlin.whenever * the set of ids, which also dictact which direction to move and when, via a filter fn. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class KeyguardUnfoldTransitionTest : SysuiTestCase() { private val kosmos = Kosmos() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index ee0cefb1949f..075d8ae0adac 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -108,10 +108,10 @@ import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.TextUtils; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.ExtendedMockito; @@ -176,7 +176,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardUpdateMonitorTest extends SysuiTestCase { private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt index c6740535d903..e7b4262419ce 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt @@ -15,8 +15,8 @@ */ package com.android.keyguard -import android.testing.AndroidTestingRunner import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -25,7 +25,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardUserSwitcherAnchorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java index 255c7d909194..c1ba39e89cf9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java @@ -32,12 +32,12 @@ import static org.mockito.Mockito.when; import android.graphics.Point; import android.hardware.biometrics.BiometricSourceType; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; import android.view.HapticFeedbackConstants; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.biometrics.UdfpsController; @@ -50,7 +50,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class LegacyLockIconViewControllerTest extends LegacyLockIconViewControllerBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt index 958013908e44..2fd3cb0f0592 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt @@ -16,8 +16,8 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.LockIconView.ICON_LOCK import com.android.systemui.doze.util.getBurnInOffset @@ -33,7 +33,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.reset import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LegacyLockIconViewControllerWithCoroutinesTest : LegacyLockIconViewControllerBaseTest() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt index 7c2550f465f6..18976e135e9c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt @@ -17,8 +17,8 @@ package com.android.keyguard import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Before @@ -32,7 +32,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NumPadAnimatorTest : SysuiTestCase() { @Mock lateinit var background: GradientDrawable diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt index 835c9ff51381..d8f2b1016657 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt @@ -16,9 +16,9 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class PinShapeHintingViewTest : SysuiTestCase() { lateinit var underTest: PinShapeHintingView diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt index d431731b710e..447cf65ba293 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt @@ -16,9 +16,9 @@ package com.android.keyguard -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class PinShapeNonHintingViewTest : SysuiTestCase() { lateinit var underTest: PinShapeNonHintingView diff --git a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt index 9f9b9a478561..c7d11ef16100 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt @@ -16,10 +16,10 @@ package com.android.keyguard import android.animation.Animator -import android.testing.AndroidTestingRunner import android.transition.TransitionValues import android.view.View import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter import com.android.systemui.SysuiTestCase @@ -32,7 +32,7 @@ import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SplitShadeTransitionAdapterTest : SysuiTestCase() { @Mock private lateinit var KeyguardClockSwitchController: KeyguardClockSwitchController diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt index fb649c8f3891..5247a89896d1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt @@ -19,7 +19,7 @@ package com.android.keyguard.mediator import android.os.Looper import android.platform.test.flag.junit.SetFlagsRule import android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase @@ -43,7 +43,7 @@ import org.mockito.MockitoAnnotations import java.util.Optional @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ScreenOnCoordinatorTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt index 2551650c47d0..0a9ce6854ca8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt @@ -16,7 +16,7 @@ package com.android.systemui -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.dump.DumpManager import org.junit.Assert.assertFalse @@ -30,7 +30,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class BootCompleteCacheTest : SysuiTestCase() { @@ -116,4 +116,4 @@ class BootCompleteCacheTest : SysuiTestCase() { verify(bootCompleteListener, never()).onBootComplete() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt index f776a63c54ae..a23e82918845 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt @@ -4,8 +4,8 @@ import android.graphics.Path import android.graphics.Rect import android.graphics.RectF import android.hardware.camera2.CameraManager -import android.testing.AndroidTestingRunner import android.util.PathParser +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.util.mockito.any @@ -19,7 +19,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class CameraAvailabilityListenerTest : SysuiTestCase() { companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java index 2e040077c227..006864489170 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java @@ -18,14 +18,17 @@ import static org.junit.Assert.assertEquals; import android.os.Looper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.concurrent.ExecutionException; @SmallTest +@RunWith(AndroidJUnit4.class) public class DependencyTest extends SysuiTestCase { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt index 588620646b73..091072f30ecd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt @@ -23,11 +23,11 @@ import android.graphics.Path import android.graphics.Rect import android.graphics.RectF import android.graphics.Region -import android.testing.AndroidTestingRunner import android.view.Display import android.view.DisplayCutout import android.view.DisplayInfo import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.internal.R @@ -42,7 +42,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DisplayCutoutBaseViewTest : SysuiTestCase() { @@ -183,4 +183,4 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { return@then true } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt index 46936d6223a1..bc12aaaa52d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt @@ -16,11 +16,11 @@ package com.android.systemui import android.graphics.Point import android.hardware.display.DisplayManagerGlobal -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Display import android.view.DisplayAdjustments import android.view.DisplayInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.keyguard.KeyguardUpdateMonitor @@ -42,7 +42,7 @@ import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class FaceScanningProviderFactoryTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt index d500b5aebcbf..fb60ba381c40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt @@ -22,11 +22,11 @@ import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.RectF import android.hardware.graphics.common.DisplayDecorationSupport -import android.testing.AndroidTestingRunner import android.view.Display import android.view.DisplayCutout import android.view.DisplayInfo import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.util.mockito.eq @@ -39,7 +39,7 @@ import org.mockito.Mockito import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ScreenDecorHwcLayerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 54a14a292ba1..997f8a844d69 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -61,7 +61,6 @@ import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Handler; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.PathParser; @@ -78,6 +77,7 @@ import android.view.WindowMetrics; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; @@ -121,7 +121,7 @@ import java.util.ArrayList; import java.util.List; @RunWithLooper -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class ScreenDecorationsTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 38095c83367d..c30bedde557d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -258,8 +258,9 @@ public class MenuViewLayerTest extends SysuiTestCase { setupEnabledAccessibilityServiceList(); mMenuViewLayer.mDismissMenuAction.run(); - final String value = Settings.Secure.getString(mSpyContext.getContentResolver(), - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + final String value = Settings.Secure.getStringForUser(mSpyContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)); assertThat(value).isEqualTo(""); } @@ -274,8 +275,9 @@ public class MenuViewLayerTest extends SysuiTestCase { ShortcutConstants.UserShortcutType.HARDWARE)).thenReturn(stubShortcutTargets); mMenuViewLayer.mDismissMenuAction.run(); - final String value = Settings.Secure.getString(mSpyContext.getContentResolver(), - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + final String value = Settings.Secure.getStringForUser(mSpyContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)); assertThat(value).isEqualTo(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString()); } @@ -445,9 +447,11 @@ public class MenuViewLayerTest extends SysuiTestCase { } private void setupEnabledAccessibilityServiceList() { - Settings.Secure.putString(mSpyContext.getContentResolver(), + Settings.Secure.putStringForUser(mSpyContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString()); + TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString(), + mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT) + ); final ResolveInfo resolveInfo = new ResolveInfo(); final ServiceInfo serviceInfo = new ServiceInfo(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java index 51f6cdb2cb89..7320bbff843e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java @@ -22,9 +22,9 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -44,7 +44,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class HearingDevicesCheckerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt index 982f033d05e5..99d36003dfef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.battery +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT @@ -24,8 +25,10 @@ import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class AccessorizedBatteryDrawableTest : SysuiTestCase() { @Test fun intrinsicSize_shieldFalse_isBatterySize() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java index 14eff2f2ef86..d940fc938c4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java @@ -31,6 +31,7 @@ import android.content.ContentResolver; import android.os.Handler; import android.provider.Settings; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -44,10 +45,12 @@ import com.android.systemui.tuner.TunerService; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest +@RunWith(AndroidJUnit4.class) public class BatteryMeterViewControllerTest extends SysuiTestCase { @Mock private BatteryMeterView mBatteryMeterView; diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt index 39cb0476e429..cac0b664ab79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.battery +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT @@ -24,8 +25,10 @@ import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class BatterySpecsTest : SysuiTestCase() { @Test fun getFullBatteryHeight_shieldFalse_returnsMainHeight() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt index 42382540d401..7fa165c19f60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt @@ -73,6 +73,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule +import org.mockito.kotlin.argumentCaptor @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -218,6 +219,13 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() { verify(kosmos.windowManager).addView(any(), any()) + var viewCaptor = argumentCaptor<View>() + verify(kosmos.windowManager).addView(viewCaptor.capture(), any()) + verify(viewCaptor.firstValue) + .announceForAccessibility( + mContext.getText(R.string.accessibility_side_fingerprint_indicator_label) + ) + // Hide alternate bouncer kosmos.keyguardBouncerRepository.setAlternateVisible(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt index d32d12c83a8d..a4936e63df8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt @@ -37,7 +37,7 @@ import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.motion.createSysUiComposeMotionTestRule -import com.android.systemui.scene.domain.interactor.sceneContainerStartable +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.testKosmos import org.junit.Before import org.junit.Rule diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt index 21516d4917b5..791f1f2e1f26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.clipboardoverlay import android.content.ContentResolver import android.content.Context import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.whenever @@ -29,6 +30,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Mock @@ -37,6 +39,7 @@ import org.mockito.MockitoAnnotations @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class ClipboardImageLoaderTest : SysuiTestCase() { @Mock private lateinit var mockContext: Context diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt index ac1f90cc6dda..b3f458821cba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt @@ -10,6 +10,7 @@ import android.service.controls.templates.TemperatureControlTemplate import android.service.controls.templates.ThumbnailTemplate import android.view.LayoutInflater import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -20,10 +21,12 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class TemperatureControlBehaviorTest : SysuiTestCase() { @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt index ef89752dd3d1..82ad30ecfbdd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.deviceentry.shared +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import junit.framework.Assert import kotlin.reflect.full.declaredMembers import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class FaceAuthReasonTest : SysuiTestCase() { @Test fun testApiReasonToUiEvent_forAllReasons_isNotNull() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt index dd741b4167e9..d79db5cc32eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.display.data.repository import android.content.Context import android.util.DisplayMetrics import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer @@ -33,9 +34,11 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class DisplayMetricsRepositoryTest : SysuiTestCase() { private lateinit var underTest: DisplayMetricsRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt index 01868ae3e5da..1c327aff9a3d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt @@ -126,6 +126,7 @@ class DisplayRepositoryTest : SysuiTestCase() { // All subscribers are done, unregister should have been called. verify(displayManager).unregisterDisplayListener(any()) } + @Test fun onDisplayAdded_propagated() = testScope.runTest { @@ -457,6 +458,16 @@ class DisplayRepositoryTest : SysuiTestCase() { assertThat(defaultDisplayOff).isFalse() } + @Test + fun displayFlow_startsWithDefaultDisplayBeforeAnyEvent() = + testScope.runTest { + setDisplays(Display.DEFAULT_DISPLAY) + + val value by latestDisplayFlowValue() + + assertThat(value?.ids()).containsExactly(Display.DEFAULT_DISPLAY) + } + private fun Iterable<Display>.ids(): List<Int> = map { it.displayId } // Wrapper to capture the displayListener. diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java index a7d7b93c4044..419f7eda9d1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import android.view.Display; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -33,10 +34,12 @@ import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.concurrent.Executor; @SmallTest +@RunWith(AndroidJUnit4.class) public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase { private Executor mExecutor; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java index 240d2d7bd35c..5a89710f6cf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import android.view.Display; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -33,10 +34,12 @@ import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.concurrent.Executor; @SmallTest +@RunWith(AndroidJUnit4.class) public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase { private Executor mExecutor; diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt index 840eb468b8c2..597629219634 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.dump +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.CoreStartable import com.android.systemui.Dumpable @@ -28,6 +29,7 @@ import com.android.systemui.util.mockito.eq import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.anyInt import org.mockito.Mockito.never @@ -39,6 +41,7 @@ import java.io.StringWriter import javax.inject.Provider @SmallTest +@RunWith(AndroidJUnit4::class) class DumpHandlerTest : SysuiTestCase() { private lateinit var dumpHandler: DumpHandler diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt index 6d5226f35e97..f331060db43e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.dump +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Dumpable import com.android.systemui.SysuiTestCase @@ -25,10 +26,12 @@ import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class DumpManagerTest : SysuiTestCase() { @Mock private lateinit var dumpable1: Dumpable diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt index 1d2afe4047c3..d09928b25084 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.dump +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -24,11 +25,13 @@ import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import java.io.PrintWriter import java.io.StringWriter @SmallTest +@RunWith(AndroidJUnit4::class) class DumpsysTableLoggerTest : SysuiTestCase() { private val logger = DumpsysTableLogger( TEST_SECTION_NAME, @@ -143,4 +146,4 @@ val TEST_DATA_VALID = List(TEST_DATA_LENGTH) { row -> List(TEST_COLUMNS.size) { col -> "data$col$row" } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt index 3b4888fc5b58..0cd2b9f03475 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.dump import android.content.BroadcastReceiver import android.content.IntentFilter import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -30,6 +31,7 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock @@ -40,6 +42,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class LogBufferFreezerTest : SysuiTestCase() { lateinit var freezer: LogBufferFreezer diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt index 3ff72028d5ca..ae6b337a3fa0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.dump +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpHandler.Companion.dump @@ -40,6 +41,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito @@ -48,6 +50,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class LogEulogizerTest : SysuiTestCase() { lateinit var eulogizer: LogBufferEulogizer diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt index 52c6e22cfcbb..9f238e64ce7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.flags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any @@ -26,6 +27,7 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify @@ -35,6 +37,7 @@ import org.mockito.MockitoAnnotations * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()! */ @SmallTest +@RunWith(AndroidJUnit4::class) class ConditionalRestarterTest : SysuiTestCase() { private lateinit var restarter: ConditionalRestarter diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt index dbe59e66b955..a1fe0f082b56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt @@ -23,6 +23,7 @@ import android.content.res.Resources import android.content.res.Resources.NotFoundException import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD import com.android.systemui.SysuiTestCase @@ -39,6 +40,7 @@ import java.util.function.Consumer import org.junit.Assert import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyString @@ -55,6 +57,7 @@ import org.mockito.MockitoAnnotations * the default. */ @SmallTest +@RunWith(AndroidJUnit4::class) class FeatureFlagsClassicDebugTest : SysuiTestCase() { private lateinit var mFeatureFlagsClassicDebug: FeatureFlagsClassicDebug diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt index 943e2124f6b4..ad8bedb22d3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt @@ -17,12 +17,14 @@ package com.android.systemui.flags import android.content.pm.PackageManager.NameNotFoundException import android.content.res.Resources +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.never @@ -34,6 +36,7 @@ import org.mockito.MockitoAnnotations * overriding, and should never return any value other than the one provided as the default. */ @SmallTest +@RunWith(AndroidJUnit4::class) class FeatureFlagsClassicReleaseTest : SysuiTestCase() { private lateinit var mFeatureFlagsClassicRelease: FeatureFlagsClassicRelease diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt index d500dd260267..dd56fa6be2d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt @@ -16,18 +16,21 @@ package com.android.systemui.flags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any import java.io.PrintWriter import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class FlagCommandTest : SysuiTestCase() { @Mock private lateinit var featureFlags: FeatureFlagsClassicDebug diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt index 5e87a6fd3fa3..593de37e0d6d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt @@ -19,6 +19,7 @@ import android.content.Context import android.database.ContentObserver import android.net.Uri import android.os.Handler +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any @@ -30,6 +31,7 @@ import java.util.function.Consumer import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify @@ -42,6 +44,7 @@ import org.mockito.MockitoAnnotations * overriding, and should never return any value other than the one provided as the default. */ @SmallTest +@RunWith(AndroidJUnit4::class) class FlagManagerTest : SysuiTestCase() { private lateinit var mFlagManager: FlagManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt index 755cc4615cc2..46b4c4b80481 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.flags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -30,6 +31,7 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -39,6 +41,7 @@ import org.mockito.MockitoAnnotations */ @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class NotOccludedConditionTest : SysuiTestCase() { private lateinit var condition: NotOccludedCondition diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt index 0fdda082b9c6..0f727cb8505c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.flags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -26,6 +27,7 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest 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.verify @@ -36,6 +38,7 @@ import org.mockito.MockitoAnnotations * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()! */ @SmallTest +@RunWith(AndroidJUnit4::class) class PluggedInConditionTest : SysuiTestCase() { private lateinit var condition: PluggedInCondition diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt index 3c965ce61aa2..1ab945adb54f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.flags import android.os.PowerManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -27,6 +28,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString @@ -37,6 +39,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class RestartDozeListenerTest : SysuiTestCase() { lateinit var restartDozeListener: RestartDozeListener diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt index 0116e5326e47..df03882b649c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.flags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -27,6 +28,7 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -35,6 +37,7 @@ import org.mockito.MockitoAnnotations * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()! */ @SmallTest +@RunWith(AndroidJUnit4::class) class ScreenIdleConditionTest : SysuiTestCase() { private lateinit var condition: ScreenIdleCondition diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt index 8a29217c33c5..008c68d0631a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.fragments import android.app.Fragment import android.os.Looper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -9,8 +10,10 @@ import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class FragmentServiceTest : SysuiTestCase() { private val fragmentHostManagerFactory: FragmentHostManager.Factory = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java index 7f55d388c34f..dc019b6bb12f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java @@ -37,6 +37,7 @@ import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.FlakyTest; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -48,6 +49,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import java.io.IOException; import java.util.concurrent.TimeUnit; @@ -55,6 +57,7 @@ import java.util.function.BooleanSupplier; @LargeTest @FlakyTest(bugId = 176891566) +@RunWith(AndroidJUnit4.class) public class GlobalActionsImeTest extends SysuiTestCase { @Rule diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt index 3a868685f4d9..2735d2f03e6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt @@ -20,6 +20,7 @@ import android.graphics.Rect import android.view.WindowInsets import android.view.WindowManager import android.view.WindowMetrics +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository @@ -33,14 +34,13 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mockito.spy import org.mockito.MockitoAnnotations import org.mockito.kotlin.doReturn import org.mockito.kotlin.whenever @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyboardDockingIndicationViewModelTest : SysuiTestCase() { private val testScope = TestScope(StandardTestDispatcher()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt new file mode 100644 index 000000000000..c4eabd84e031 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.binder + +import android.platform.test.annotations.EnableFlags +import android.testing.TestableLooper +import android.view.View +import android.view.layoutInflater +import android.view.mockedLayoutInflater +import android.view.windowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.domain.interactor.givenCanShowAlternateBouncer +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.kotlin.isNull + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class AlternateBouncerViewBinderTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val mockedAltBouncerView = + spy(kosmos.layoutInflater.inflate(R.layout.alternate_bouncer, null, false)) + + @Before + fun setup() { + whenever( + kosmos.mockedLayoutInflater.inflate( + eq(R.layout.alternate_bouncer), + isNull(), + anyBoolean() + ) + ) + .thenReturn(mockedAltBouncerView) + kosmos.alternateBouncerViewBinder.start() + } + + @Test + @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + fun addViewToWindowManager() { + testScope.runTest { + kosmos.givenCanShowAlternateBouncer() + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + testScope, + ) + verify(kosmos.windowManager).addView(any(), any()) + } + } + + @Test + @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + fun viewRemovedImmediatelyIfAlreadyAttachedToWindow() { + testScope.runTest { + kosmos.givenCanShowAlternateBouncer() + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + testScope, + ) + verify(kosmos.windowManager).addView(any(), any()) + whenever(mockedAltBouncerView.isAttachedToWindow).thenReturn(true) + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + verify(kosmos.windowManager).removeView(any()) + } + } + + @Test + @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + fun viewNotRemovedUntilAttachedToWindow() { + testScope.runTest { + kosmos.givenCanShowAlternateBouncer() + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + testScope, + ) + verify(kosmos.windowManager).addView(any(), any()) + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + verify(kosmos.windowManager, never()).removeView(any()) + givenAltBouncerViewAttachedToWindow() + verify(kosmos.windowManager).removeView(any()) + } + } + + private fun givenAltBouncerViewAttachedToWindow() { + val attachStateChangeListenerCaptor = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java) + verify(mockedAltBouncerView, atLeastOnce()) + .addOnAttachStateChangeListener(attachStateChangeListenerCaptor.capture()) + attachStateChangeListenerCaptor.allValues.onEach { + it.onViewAttachedToWindow(mockedAltBouncerView) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt index 7787a7fa6af1..9055495b9f23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.binder import android.testing.TestableLooper.RunWithLooper import android.view.RemoteAnimationTarget +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.SysuiTestCase @@ -34,6 +35,7 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.doAnswer @@ -42,6 +44,7 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWithLooper(setAsMainLooper = true) @kotlinx.coroutines.ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule(this) @@ -155,4 +158,4 @@ class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() { // Releasing the surface should immediately cancel animators. assertFalse(checkNotNull(isAnimatingSurface)) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoSettingsFormatTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoSettingsFormatTest.kt index 02c9debcbe1f..aa4a6d17123c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoSettingsFormatTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoSettingsFormatTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.log.echo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.core.LogLevel @@ -24,8 +25,10 @@ import com.android.systemui.log.echo.EchoOverrideType.BUFFER import com.android.systemui.log.echo.EchoOverrideType.TAG import kotlin.test.assertEquals import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class LogcatEchoSettingsFormatTest : SysuiTestCase() { private val format = LogcatEchoSettingFormat() diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoTrackerDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoTrackerDebugTest.kt index 7967134eb150..a5f50af55a4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoTrackerDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/echo/LogcatEchoTrackerDebugTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.log.echo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.core.LogLevel.DEBUG @@ -39,11 +40,13 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class LogcatEchoTrackerDebugTest : SysuiTestCase() { private val dispatcher = StandardTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt index 1d182cfec49c..e55cb12bc0e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.log.table +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX @@ -35,9 +36,11 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class LogDiffsForTableTest : SysuiTestCase() { private val testDispatcher = UnconfinedTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt index 43e430fd7fa2..8d608111744e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.log.table +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX @@ -23,8 +24,10 @@ import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class TableChangeTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt index 8ba76437c725..8c62bc2e0207 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.log.table +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -26,9 +27,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.Test +import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class TableLogBufferFactoryTest : SysuiTestCase() { private val dumpManager: DumpManager = mock() private val systemClock = FakeSystemClock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt index 5c9a0033cb72..ace562b93c2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.log.table +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogcatEchoTracker @@ -35,9 +36,11 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class TableLogBufferTest : SysuiTestCase() { private lateinit var underTest: TableLogBuffer diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java index 544350c7e24d..1d4b0903b579 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java @@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L, - InstanceId.fakeInstanceId(-1), -1, false, null); + InstanceId.fakeInstanceId(-1), -1, false, null, -1, false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index 4da56b51e423..c974e0dde5e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -740,11 +740,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) - val controlCommonModel = - MediaCommonModel.MediaControl( - MediaDataLoadingModel.Loaded(dataMain.instanceId), - true - ) + val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) + var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) repository.setOrderedMedia() assertThat(currentMedia).containsExactly(controlCommonModel) @@ -758,7 +755,15 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead - val dataCurrentAndActive = dataCurrent.copy(active = true) + val dataCurrentAndActive = + dataMain.copy(active = true, lastActive = clock.elapsedRealtime()) + controlCommonModel = + controlCommonModel.copy( + mediaLoadingModel.copy( + receivedSmartspaceCardLatency = 100, + isSsReactivated = true + ) + ) assertThat(currentMedia).containsExactly(controlCommonModel) assertThat( hasActiveMediaOrRecommendation( @@ -800,11 +805,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val currentMedia by collectLastValue(repository.currentMedia) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) - val controlCommonModel = - MediaCommonModel.MediaControl( - MediaDataLoadingModel.Loaded(dataMain.instanceId), - true - ) + val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) + var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true) val recsCommonModel = MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) @@ -824,7 +826,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead - val dataCurrentAndActive = dataCurrent.copy(active = true) + val dataCurrentAndActive = + dataMain.copy(active = true, lastActive = clock.elapsedRealtime()) verify(listener) .onMediaDataLoaded( eq(KEY), @@ -849,6 +852,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() // Smartspace update should also be propagated but not prioritized. + controlCommonModel = + controlCommonModel.copy( + mediaLoadingModel.copy( + receivedSmartspaceCardLatency = 100, + isSsReactivated = true + ) + ) assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) @@ -909,7 +919,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - val dataCurrentAndActive = dataCurrent.copy(active = true) + val dataCurrentAndActive = + dataMain.copy(active = true, lastActive = clock.elapsedRealtime()) verify(listener) .onMediaDataLoaded( eq(KEY), @@ -1063,11 +1074,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) ) - val controlCommonModel = - MediaCommonModel.MediaControl( - MediaDataLoadingModel.Loaded(dataMain.instanceId), - true - ) + val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) + var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) @@ -1086,7 +1094,15 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead - val dataCurrentAndActive = dataCurrent.copy(active = true) + val dataCurrentAndActive = + dataMain.copy(active = true, lastActive = clock.elapsedRealtime()) + controlCommonModel = + controlCommonModel.copy( + mediaLoadingModel.copy( + receivedSmartspaceCardLatency = 100, + isSsReactivated = true + ) + ) verify(listener) .onMediaDataLoaded( eq(KEY), diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt index 473dc4712c25..868145d1403a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.media.controls.shared import android.app.smartspace.SmartspaceAction import android.graphics.drawable.Icon +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase @@ -26,8 +27,10 @@ import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class SmartspaceMediaDataTest : SysuiTestCase() { private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt index 2a8967edf13d..64acc5965d45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt @@ -24,6 +24,7 @@ import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo import android.media.AudioManager import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.media.DeviceIconUtil import com.android.settingslib.media.LocalMediaManager @@ -35,6 +36,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock 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.never @@ -45,6 +47,7 @@ import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { private lateinit var muteAwaitConnectionManager: MediaMuteAwaitConnectionManager @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt index d9453d67848f..b9aa02967acc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.media.nearby import android.media.INearbyMediaDevicesProvider import android.media.INearbyMediaDevicesUpdateCallback +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import android.media.NearbyDevice @@ -10,6 +11,7 @@ import com.android.systemui.statusbar.CommandQueue import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.anyInt @@ -20,6 +22,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class NearbyMediaDevicesManagerTest : SysuiTestCase() { private lateinit var manager: NearbyMediaDevicesManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index d82819397f08..77807bf7915b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.taptotransfer import android.app.StatusBarManager import android.content.Context import android.media.MediaRoute2Info +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver @@ -35,6 +36,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -43,6 +45,7 @@ import java.io.StringWriter import java.util.concurrent.Executor @SmallTest +@RunWith(AndroidJUnit4::class) class MediaTttCommandLineHelperTest : SysuiTestCase() { private val inlineExecutor = Executor { command -> command.run() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt index b322bb7d6b46..ab5c893644b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.media.taptotransfer.common +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -27,9 +28,11 @@ import java.io.PrintWriter import java.io.StringWriter import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.mock @SmallTest +@RunWith(AndroidJUnit4::class) class MediaTttLoggerUtilsTest : SysuiTestCase() { private lateinit var buffer: LogBuffer diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt index 5c6d9131e1f0..02123ba3ba02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.taptotransfer.common import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -30,12 +31,14 @@ import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class MediaTttUtilsTest : SysuiTestCase() { private lateinit var appIconFromPackageName: Drawable diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt index 64f3fd304ca6..cee71b5266a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.media.taptotransfer.receiver +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -27,9 +28,11 @@ import java.io.PrintWriter import java.io.StringWriter import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.mock @SmallTest +@RunWith(AndroidJUnit4::class) class MediaTttReceiverLoggerTest : SysuiTestCase() { private lateinit var buffer: LogBuffer diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLoggerTest.kt index f55771362cb2..352443a99acc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLoggerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.media.taptotransfer.receiver +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake @@ -7,8 +8,10 @@ import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class MediaTttReceiverUiEventLoggerTest : SysuiTestCase() { private lateinit var uiEventLoggerFake: UiEventLoggerFake private lateinit var logger: MediaTttReceiverUiEventLogger diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt index ee3704c1e9e9..af8b3edf38c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.media.taptotransfer.sender +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase @@ -28,9 +29,11 @@ import java.io.PrintWriter import java.io.StringWriter import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.mock @SmallTest +@RunWith(AndroidJUnit4::class) class MediaTttSenderLoggerTest : SysuiTestCase() { private lateinit var buffer: LogBuffer diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt index bf26a2f53b4c..30b578a77e96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.media.taptotransfer.sender +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake @@ -7,8 +8,10 @@ import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class MediaTttSenderUiEventLoggerTest : SysuiTestCase() { private lateinit var uiEventLoggerFake: UiEventLoggerFake private lateinit var logger: MediaTttSenderUiEventLogger diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt index f4c5ccfc8388..b337ccfda772 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt @@ -22,6 +22,7 @@ import android.graphics.Rect import android.os.Bundle import android.view.View import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN @@ -36,6 +37,7 @@ import com.google.common.truth.Truth.assertThat import java.util.Optional import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.any @@ -43,6 +45,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.verify @SmallTest +@RunWith(AndroidJUnit4::class) class MediaProjectionRecentsViewControllerTest : SysuiTestCase() { @get:Rule val expect: Expect = Expect.create() diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt index 9630ee32d456..55e52b780488 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt @@ -27,6 +27,7 @@ import android.view.WindowManager import android.view.WindowMetrics import androidx.core.view.WindowInsetsCompat.Type import androidx.lifecycle.LifecycleOwner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener @@ -38,8 +39,10 @@ import com.google.common.truth.Truth.assertThat import kotlin.math.min import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class TaskPreviewSizeProviderTest : SysuiTestCase() { private val lifecycleOwner = mock<LifecycleOwner>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt index fe18454ec06c..cc722aa40a1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt @@ -5,14 +5,17 @@ import android.graphics.Rect import android.view.WindowManager import android.view.WindowMetrics import androidx.core.view.WindowInsetsCompat +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class WindowMetricsProviderImplTest : SysuiTestCase() { private val windowManager = mock<WindowManager>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java index 2ff660f793cb..bfbb7ceee6b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.res.Configuration; +import android.os.Handler; import android.view.IWindowManager; import android.view.accessibility.AccessibilityManager; @@ -65,6 +66,8 @@ import dagger.Lazy; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -120,6 +123,10 @@ public class NavBarHelperTest extends SysuiTestCase { EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory; @Mock NotificationShadeWindowController mNotificationShadeWindowController; + @Mock + Handler mBgHandler; + + @Captor ArgumentCaptor<Runnable> mRunnableArgumentCaptor; ConfigurationController mConfigurationController = new FakeConfigurationController(); private AccessibilityManager.AccessibilityServicesStateChangeListener @@ -149,7 +156,7 @@ public class NavBarHelperTest extends SysuiTestCase { () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class), mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker, mDisplayTracker, mNotificationShadeWindowController, mConfigurationController, - mDumpManager, mCommandQueue, mSynchronousExecutor); + mDumpManager, mCommandQueue, mSynchronousExecutor, mBgHandler); } @Test @@ -203,8 +210,10 @@ public class NavBarHelperTest extends SysuiTestCase { .updateAccessibilityServicesState(); verify(mNavbarTaskbarStateUpdater, times(1)) .updateAssistantAvailable(anyBoolean(), anyBoolean()); + verify(mBgHandler).post(mRunnableArgumentCaptor.capture()); + mRunnableArgumentCaptor.getValue().run(); verify(mNavbarTaskbarStateUpdater, times(1)) - .updateRotationWatcherState(anyInt()); + .updateRotationWatcherState(anyInt(), anyBoolean()); verify(mNavbarTaskbarStateUpdater, times(1)) .updateWallpaperVisibility(anyBoolean(), anyInt()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java index d5361acba308..df8eafe2ea2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java @@ -21,6 +21,7 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification; +import static com.android.wm.shell.Flags.enableTaskbarOnPhones; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -142,10 +143,11 @@ public class NavigationBarControllerImplTest extends SysuiTestCase { @Test public void testCreateNavigationBarsIncludeDefaultTrue() { - assumeFalse(enableTaskbarNavbarUnification()); + assumeFalse(enableTaskbarNavbarUnification() && enableTaskbarOnPhones()); // Large screens may be using taskbar and the logic is different mNavigationBarController.mIsLargeScreen = false; + mNavigationBarController.mIsPhone = true; doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any()); mNavigationBarController.createNavigationBars(true, null); @@ -291,6 +293,17 @@ public class NavigationBarControllerImplTest extends SysuiTestCase { @Test public void testShouldRenderTaskbar_taskbarNotRenderedOnPhone() { + assumeFalse(enableTaskbarOnPhones()); + + mNavigationBarController.mIsLargeScreen = false; + mNavigationBarController.mIsPhone = true; + assertFalse(mNavigationBarController.supportsTaskbar()); + } + + @Test + public void testShouldRenderTaskbar_taskbarRenderedOnPhone() { + assumeTrue(enableTaskbarNavbarUnification() && enableTaskbarOnPhones()); + mNavigationBarController.mIsLargeScreen = false; mNavigationBarController.mIsPhone = true; assertFalse(mNavigationBarController.supportsTaskbar()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 2b60f650e162..982a26904de9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -179,6 +179,9 @@ public class NavigationBarTest extends SysuiTestCase { private SysUiState mMockSysUiState; @Mock private Handler mHandler; + + @Mock + private Handler mBgHandler; @Mock private UserTracker mUserTracker; @Mock @@ -277,7 +280,8 @@ public class NavigationBarTest extends SysuiTestCase { mEdgeBackGestureHandlerFactory, mock(IWindowManager.class), mock(UserTracker.class), mock(DisplayTracker.class), mNotificationShadeWindowController, mock(ConfigurationController.class), - mock(DumpManager.class), mock(CommandQueue.class), mSynchronousExecutor)); + mock(DumpManager.class), mock(CommandQueue.class), mSynchronousExecutor, + mBgHandler)); mNavigationBar = createNavBar(mContext); mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal); }); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt index bba275e993fd..5ed8a11e862f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt @@ -1,6 +1,7 @@ package com.android.systemui.navigationbar import android.app.ActivityManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -19,6 +20,7 @@ import com.android.wm.shell.pip.Pip import java.util.Optional import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.Mockito.any @@ -30,6 +32,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class TaskbarDelegateTest : SysuiTestCase() { val DISPLAY_ID = 0; val MODE_GESTURE = 0; @@ -108,4 +111,4 @@ class TaskbarDelegateTest : SysuiTestCase() { ArgumentMatchers.eq(true) ) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt index 9e5d3bdb179c..cc3e27c71564 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -26,9 +27,11 @@ import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.mock @SmallTest +@RunWith(AndroidJUnit4::class) class QSDisableFlagsLoggerTest : SysuiTestCase() { private val buffer = 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 1c86638c9f27..03483c98856e 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 @@ -20,6 +20,8 @@ import static android.platform.test.flag.junit.FlagsParameterization.allCombinat import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -279,4 +281,23 @@ public class TileServiceManagerTest extends SysuiTestCase { verify(mTileLifecycle, never()).onStopListening(); verify(mTileLifecycle, never()).executeSetBindService(false); } + + @Test + public void testNoExtraPendingBindIfAlreadyBound() { + mTileServiceManager.startLifecycleManagerAndAddTile(); + + // As part of adding the tile, it will be bound and it will send a start successful to + // TileServices. startSuccessful will clear pending bind + mTileServiceManager.clearPendingBind(); + + // Assume we are still bound + when(mTileLifecycle.isBound()).thenReturn(true); + + // And we want to bind again + mTileServiceManager.setBindAllowed(true); + mTileServiceManager.setBindRequested(true); + + // Then the tile doesn't have pending bind + assertThat(mTileServiceManager.hasPendingBind()).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt index b0aa6ddf44ce..7c95dfe61a54 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.qs.panels.data import android.content.Context import android.content.SharedPreferences import android.content.pm.UserInfo -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -36,7 +36,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class QSPreferencesRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = with(kosmos) { qsPreferencesRepository } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt index 9b08432e290f..7ad904e9b436 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.panels.domain.interactor import android.content.pm.UserInfo -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class IconLabelVisibilityInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = with(kosmos) { iconLabelVisibilityInteractor } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index 50cf5cc5672e..9f12b189d76a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -39,7 +39,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.classifier.FalsingManagerFake; @@ -54,14 +53,11 @@ import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.connectivity.WifiIndicators; import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository; -import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor; import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastController.CastDevice; +import com.android.systemui.statusbar.policy.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import kotlinx.coroutines.test.TestScope; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -105,12 +101,10 @@ public class CastTileTest extends SysuiTestCase { @Mock private QsEventLogger mUiEventLogger; - private WifiInteractor mWifiInteractor; private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter(); private final FakeConnectivityRepository mConnectivityRepository = new FakeConnectivityRepository(); private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); - private final TestScope mTestScope = TestScopeProvider.getTestScope(); private TestableLooper mTestableLooper; private CastTile mCastTile; @@ -172,8 +166,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateActive_wifiEnabledAndCasting() { createAndStartTileOldImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastController.CastDevice.STATE_CONNECTED; + CastDevice device = createConnectedCastDevice(); List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); @@ -232,8 +225,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void stateActive_wifiConnectedAndCasting_newPipeline() { createAndStartTileNewImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastDevice.STATE_CONNECTED; + CastDevice device = createConnectedCastDevice(); List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); @@ -248,8 +240,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void stateActive_ethernetConnectedAndCasting_newPipeline() { createAndStartTileNewImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastDevice.STATE_CONNECTED; + CastDevice device = createConnectedCastDevice(); List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); @@ -286,8 +277,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testStateActive_hotspotEnabledAndConnectedAndCasting() { createAndStartTileOldImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastController.CastDevice.STATE_CONNECTED; + CastDevice device = createConnectedCastDevice(); List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); @@ -309,9 +299,13 @@ public class CastTileTest extends SysuiTestCase { @Test public void testHandleClick_castDevicePresent() { createAndStartTileOldImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastDevice.STATE_CONNECTED; - device.tag = mock(MediaRouter.RouteInfo.class); + CastDevice device = new CastDevice( + "id", + /* name= */ null, + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaRouter, + /* tag= */ mock(MediaRouter.RouteInfo.class)); List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); @@ -327,9 +321,13 @@ public class CastTileTest extends SysuiTestCase { @Test public void testHandleClick_projectionOnly() { createAndStartTileOldImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastDevice.STATE_CONNECTED; - device.tag = mock(MediaProjectionInfo.class); + CastDevice device = new CastDevice( + "id", + /* name= */ null, + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaProjection, + /* tag= */ mock(MediaProjectionInfo.class)); List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); @@ -344,31 +342,40 @@ public class CastTileTest extends SysuiTestCase { @Test public void testUpdateState_projectionOnly() { createAndStartTileOldImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastDevice.STATE_CONNECTED; - device.tag = mock(MediaProjectionInfo.class); - device.name = "Test Projection Device"; + CastDevice device = new CastDevice( + "id", + /* name= */ "Test Projection Device", + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaProjection, + /* tag= */ mock(MediaProjectionInfo.class)); List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); enableWifiAndProcessMessages(); assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); - assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(device.name)); + assertTrue(mCastTile.getState().secondaryLabel.toString() + .startsWith("Test Projection Device")); } @Test public void testUpdateState_castingAndProjection() { createAndStartTileOldImpl(); - CastController.CastDevice casting = new CastController.CastDevice(); - casting.state = CastDevice.STATE_CONNECTED; - casting.tag = mock(RouteInfo.class); - casting.name = "Test Casting Device"; - - CastController.CastDevice projection = new CastController.CastDevice(); - projection.state = CastDevice.STATE_CONNECTED; - projection.tag = mock(MediaProjectionInfo.class); - projection.name = "Test Projection Device"; + CastDevice casting = new CastDevice( + "id1", + /* name= */ "Test Casting Device", + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaRouter, + /* tag= */ mock(RouteInfo.class)); + CastDevice projection = new CastDevice( + "id2", + /* name= */ "Test Projection Device", + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaProjection, + /* tag= */ mock(MediaProjectionInfo.class)); List<CastDevice> devices = new ArrayList<>(); devices.add(casting); @@ -379,22 +386,27 @@ public class CastTileTest extends SysuiTestCase { // Note here that the tile should be active, and should choose casting over projection. assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); - assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(casting.name)); + assertTrue(mCastTile.getState().secondaryLabel.toString() + .startsWith("Test Casting Device")); } @Test public void testUpdateState_connectedAndConnecting() { createAndStartTileOldImpl(); - CastController.CastDevice connecting = new CastController.CastDevice(); - connecting.state = CastDevice.STATE_CONNECTING; - connecting.tag = mock(RouteInfo.class); - connecting.name = "Test Casting Device"; - - CastController.CastDevice connected = new CastController.CastDevice(); - connected.state = CastDevice.STATE_CONNECTED; - connected.tag = mock(RouteInfo.class); - connected.name = "Test Casting Device"; - + CastDevice connecting = new CastDevice( + "id", + /* name= */ "Test Connecting Device", + /* description= */ null, + /* state= */ CastDevice.CastState.Connecting, + /* origin= */ CastDevice.CastOrigin.MediaRouter, + /* tag= */ mock(RouteInfo.class)); + CastDevice connected = new CastDevice( + "id", + /* name= */ "Test Connected Device", + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaRouter, + /* tag= */ mock(RouteInfo.class)); List<CastDevice> devices = new ArrayList<>(); devices.add(connecting); devices.add(connected); @@ -404,7 +416,8 @@ public class CastTileTest extends SysuiTestCase { // Tile should be connected and always prefer the connected device. assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); - assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name)); + assertTrue(mCastTile.getState().secondaryLabel.toString() + .startsWith("Test Connected Device")); } @Test @@ -427,8 +440,7 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_casting_projection() { createAndStartTileOldImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastController.CastDevice.STATE_CONNECTED; + CastDevice device = createConnectedCastDevice(); List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); @@ -441,9 +453,14 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_connecting_projection() { createAndStartTileOldImpl(); - CastController.CastDevice connecting = new CastController.CastDevice(); - connecting.state = CastDevice.STATE_CONNECTING; - connecting.name = "Test Casting Device"; + CastDevice connecting = new CastDevice( + "id", + /* name= */ + "Test Projection Device", + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaProjection, + /* tag= */ mock(MediaProjectionInfo.class)); List<CastDevice> devices = new ArrayList<>(); devices.add(connecting); @@ -457,9 +474,14 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_casting_mediaRoute() { createAndStartTileOldImpl(); - CastController.CastDevice device = new CastController.CastDevice(); - device.state = CastDevice.STATE_CONNECTED; - device.tag = mock(MediaRouter.RouteInfo.class); + CastDevice device = new CastDevice( + "id", + /* name= */ "Test Router Device", + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaRouter, + /* tag= */ mock(RouteInfo.class)); + List<CastDevice> devices = new ArrayList<>(); devices.add(device); when(mController.getCastDevices()).thenReturn(devices); @@ -472,11 +494,13 @@ public class CastTileTest extends SysuiTestCase { @Test public void testExpandView_connecting_mediaRoute() { createAndStartTileOldImpl(); - CastController.CastDevice connecting = new CastController.CastDevice(); - connecting.state = CastDevice.STATE_CONNECTING; - connecting.tag = mock(RouteInfo.class); - connecting.name = "Test Casting Device"; - + CastDevice connecting = new CastDevice( + "id", + /* name= */ "Test Router Device", + /* description= */ null, + /* state= */ CastDevice.CastState.Connecting, + /* origin= */ CastDevice.CastOrigin.MediaRouter, + /* tag= */ mock(RouteInfo.class)); List<CastDevice> devices = new ArrayList<>(); devices.add(connecting); when(mController.getCastDevices()).thenReturn(devices); @@ -567,4 +591,14 @@ public class CastTileTest extends SysuiTestCase { hotspotCallbackArgumentCaptor.capture()); mHotspotCallback = hotspotCallbackArgumentCaptor.getValue(); } + + private CastDevice createConnectedCastDevice() { + return new CastDevice( + "id", + /* name= */ null, + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaProjection, + /* tag= */ null); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt index 06833213671d..79cb51ab3e10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -20,6 +20,7 @@ import android.os.Handler import android.service.quicksettings.Tile import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R @@ -54,12 +55,14 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidJUnit4::class) class InternetTileNewImplTest : SysuiTestCase() { lateinit var underTest: InternetTileNewImpl diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index 503c52f89b0a..ce1a885098d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.recordissue import android.app.Dialog -import android.content.SharedPreferences import android.os.UserHandle import android.testing.TestableLooper import android.widget.Button @@ -57,6 +56,7 @@ import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -71,7 +71,6 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var state: IssueRecordingState - @Mock private lateinit var sharedPreferences: SharedPreferences @Mock private lateinit var screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate @Mock private lateinit var screenCaptureDisabledDialog: SystemUIDialog @@ -192,7 +191,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { anyInt(), eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER) ) - verify(factory).create(any<ScreenCapturePermissionDialogDelegate>()) + verify(factory, times(2)).create(any(SystemUIDialog.Delegate::class.java)) } @Test @@ -213,7 +212,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { anyInt(), eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER) ) - verify(factory, never()).create(any<ScreenCapturePermissionDialogDelegate>()) + verify(factory, never()).create() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index 0d7a9e4d2430..f866f740345e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -35,8 +35,8 @@ import android.content.Intent; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; @@ -60,7 +60,7 @@ import org.mockito.MockitoAnnotations; import java.io.IOException; import java.util.concurrent.Executor; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class RecordingServiceTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt index 943245150e64..db607fd56643 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt @@ -18,10 +18,10 @@ package com.android.systemui.screenrecord import android.content.Intent import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.widget.Spinner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Dependency import com.android.systemui.SysuiTestCase @@ -55,7 +55,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt new file mode 100644 index 000000000000..9331c8df46d4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenrecord.data.model + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test + +@SmallTest +class ScreenRecordModelTest : SysuiTestCase() { + @Test + fun countdownSeconds_millis0_is0() { + assertThat(0L.toCountdownSeconds()).isEqualTo(0) + assertThat(ScreenRecordModel.Starting(0L).countdownSeconds).isEqualTo(0) + } + + @Test + fun countdownSeconds_millis500_isOne() { + assertThat(500L.toCountdownSeconds()).isEqualTo(1) + assertThat(ScreenRecordModel.Starting(500L).countdownSeconds).isEqualTo(1) + } + + @Test + fun countdownSeconds_millis999_isOne() { + assertThat(999L.toCountdownSeconds()).isEqualTo(1) + assertThat(ScreenRecordModel.Starting(999L).countdownSeconds).isEqualTo(1) + } + + @Test + fun countdownSeconds_millis1000_isOne() { + assertThat(1000L.toCountdownSeconds()).isEqualTo(1) + assertThat(ScreenRecordModel.Starting(1000L).countdownSeconds).isEqualTo(1) + } + + @Test + fun countdownSeconds_millis1499_isOne() { + assertThat(1499L.toCountdownSeconds()).isEqualTo(1) + assertThat(ScreenRecordModel.Starting(1499L).countdownSeconds).isEqualTo(1) + } + + @Test + fun countdownSeconds_millis1500_isTwo() { + assertThat(1500L.toCountdownSeconds()).isEqualTo(2) + assertThat(ScreenRecordModel.Starting(1500L).countdownSeconds).isEqualTo(2) + } + + @Test + fun countdownSeconds_millis1999_isTwo() { + assertThat(1599L.toCountdownSeconds()).isEqualTo(2) + assertThat(ScreenRecordModel.Starting(1599L).countdownSeconds).isEqualTo(2) + } + + @Test + fun countdownSeconds_millis2000_isTwo() { + assertThat(2000L.toCountdownSeconds()).isEqualTo(2) + assertThat(ScreenRecordModel.Starting(2000L).countdownSeconds).isEqualTo(2) + } + + @Test + fun countdownSeconds_millis2500_isThree() { + assertThat(2500L.toCountdownSeconds()).isEqualTo(3) + assertThat(ScreenRecordModel.Starting(2500L).countdownSeconds).isEqualTo(3) + } + + @Test + fun countdownSeconds_millis2999_isThree() { + assertThat(2999L.toCountdownSeconds()).isEqualTo(3) + assertThat(ScreenRecordModel.Starting(2999L).countdownSeconds).isEqualTo(3) + } + + @Test + fun countdownSeconds_millis3000_isThree() { + assertThat(3000L.toCountdownSeconds()).isEqualTo(3) + assertThat(ScreenRecordModel.Starting(3000L).countdownSeconds).isEqualTo(3) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt index 61ea437f10dd..aceea909e595 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.screenrecord.data.repository +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -28,6 +29,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -35,6 +37,7 @@ import org.mockito.kotlin.whenever @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class ScreenRecordRepositoryTest : SysuiTestCase() { private val kosmos = Kosmos() private val testScope = kosmos.testScope diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt index 36639d4b7ca4..67eaf7b9d19e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.truth.content.IntentSubject.assertThat as assertThatIntent import androidx.test.filters.SmallTest import com.android.systemui.res.R @@ -29,9 +30,11 @@ import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.`when` as whenever @SmallTest +@RunWith(AndroidJUnit4::class) class ActionIntentCreatorTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt index 5cd3f66d2cbf..612d646bb7d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.screenshot import android.content.Intent +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.os.Process.myUserHandle import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableContext import com.android.systemui.Flags import com.android.systemui.SysuiTestCase @@ -36,7 +36,7 @@ import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ActionIntentExecutorTest : SysuiTestCase() { private val scheduler = TestCoroutineScheduler() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt index 6f5c56eb9148..148a2e56f2a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt @@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.screenshot.ui.viewmodel.PreviewAction import com.google.common.truth.Truth.assertThat import java.util.UUID import kotlin.test.Test @@ -60,9 +61,9 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() { actionsProvider = createActionsProvider() - val previewActionCaptor = argumentCaptor<() -> Unit>() + val previewActionCaptor = argumentCaptor<PreviewAction>() verify(actionsCallback).providePreviewAction(previewActionCaptor.capture()) - previewActionCaptor.firstValue.invoke() + previewActionCaptor.firstValue.onClick.invoke() verifyNoMoreInteractions(actionExecutor) } @@ -102,14 +103,14 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { fun actionAccessed_whilePending_launchesMostRecentAction() = runTest { actionsProvider = createActionsProvider() - val previewActionCaptor = argumentCaptor<() -> Unit>() + val previewActionCaptor = argumentCaptor<PreviewAction>() verify(actionsCallback).providePreviewAction(previewActionCaptor.capture()) val actionButtonCaptor = argumentCaptor<() -> Unit>() verify(actionsCallback, times(2)) .provideActionButton(any(), any(), actionButtonCaptor.capture()) actionButtonCaptor.firstValue.invoke() - previewActionCaptor.firstValue.invoke() + previewActionCaptor.firstValue.onClick.invoke() actionButtonCaptor.secondValue.invoke() actionsProvider.setCompletedScreenshot(validResult) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java index c6ce51a28dd3..ba330f7b6ec0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java @@ -20,10 +20,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class DraggableConstraintLayoutTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt index 924b872546c7..b31d21e7f8b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt @@ -4,12 +4,12 @@ import android.graphics.drawable.Drawable import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.Guideline +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.screenshot.message.LabeledIcon @@ -31,7 +31,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MessageContainerControllerTest : SysuiTestCase() { lateinit var messageContainer: MessageContainerController diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt index 2a3c31aee6e7..29e7a8a4ca85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt @@ -16,9 +16,10 @@ package com.android.systemui.screenshot -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.screenshot.ui.viewmodel.PreviewAction import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import java.util.UUID import kotlin.test.Test @@ -29,13 +30,13 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ScreenshotActionsControllerTest : SysuiTestCase() { private val screenshotData = mock<ScreenshotData>() private val actionExecutor = mock<ActionExecutor>() private val viewModel = mock<ScreenshotViewModel>() - private val onClick = mock<() -> Unit>() + private val previewAction = PreviewAction("description", onClick = {}) private lateinit var actionsController: ScreenshotActionsController private lateinit var fakeActionsProvider1: FakeActionsProvider @@ -43,6 +44,7 @@ class ScreenshotActionsControllerTest : SysuiTestCase() { private val actionsProviderFactory = object : ScreenshotActionsProvider.Factory { var isFirstCall = true + override fun create( requestId: UUID, request: ScreenshotData, @@ -69,16 +71,16 @@ class ScreenshotActionsControllerTest : SysuiTestCase() { @Test fun setPreview_onCurrentScreenshot_updatesViewModel() { actionsController.setCurrentScreenshot(screenshotData) - fakeActionsProvider1.callPreview(onClick) + fakeActionsProvider1.callPreview(previewAction) - verify(viewModel).setPreviewAction(onClick) + verify(viewModel).setPreviewAction(previewAction) } @Test fun setPreview_onNonCurrentScreenshot_doesNotUpdateViewModel() { actionsController.setCurrentScreenshot(screenshotData) actionsController.setCurrentScreenshot(screenshotData) - fakeActionsProvider1.callPreview(onClick) + fakeActionsProvider1.callPreview(previewAction) verify(viewModel, never()).setPreviewAction(any()) } @@ -87,8 +89,8 @@ class ScreenshotActionsControllerTest : SysuiTestCase() { private val actionsCallback: ScreenshotActionsController.ActionsCallback ) : ScreenshotActionsProvider { - fun callPreview(onClick: () -> Unit) { - actionsCallback.providePreviewAction(onClick) + fun callPreview(previewAction: PreviewAction) { + actionsCallback.providePreviewAction(previewAction) } override fun onScrollChipReady(onClick: Runnable) {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt index f8a8a6830669..3ed09770b189 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.screenshot import android.content.ComponentName +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.graphics.Insets import android.graphics.Rect import android.os.UserHandle @@ -25,7 +26,9 @@ import android.view.WindowManager import com.android.internal.util.ScreenshotRequest import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class ScreenshotDataTest { private val type = WindowManager.TAKE_SCREENSHOT_FULLSCREEN private val source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 2a9aca7f5a35..89c13b0468f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -34,8 +34,8 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Handler; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -50,7 +50,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) /** * Tests for exception handling and bitmap configuration in adding smart actions to Screenshot * Notification. diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt index 92c240404b24..5987e74e02ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.screenshot import android.media.MediaPlayer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock @@ -29,11 +30,13 @@ import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class ScreenshotSoundControllerTest : SysuiTestCase() { private val soundProvider = mock<ScreenshotSoundProvider>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java index 83c949793447..471fdc0b8fde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java @@ -28,8 +28,8 @@ import static org.mockito.Mockito.when; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class SmartActionsReceiverTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index bf7d909380df..0b81b5e6de35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -3,7 +3,6 @@ package com.android.systemui.screenshot import android.content.ComponentName import android.graphics.Bitmap import android.net.Uri -import android.testing.AndroidTestingRunner import android.view.Display import android.view.Display.TYPE_EXTERNAL import android.view.Display.TYPE_INTERNAL @@ -12,6 +11,7 @@ import android.view.Display.TYPE_VIRTUAL import android.view.Display.TYPE_WIFI import android.view.WindowManager import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.util.ScreenshotRequest @@ -22,7 +22,6 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.kotlinArgumentCaptor as ArgumentCaptor import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.lang.IllegalStateException @@ -39,12 +38,11 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class TakeScreenshotExecutorTest : SysuiTestCase() { - private val controller0 = mock<ScreenshotController>() - private val controller1 = mock<ScreenshotController>() + private val controller = mock<ScreenshotController>() private val notificationsController0 = mock<ScreenshotNotificationsController>() private val notificationsController1 = mock<ScreenshotNotificationsController>() private val controllerFactory = mock<ScreenshotController.Factory>() @@ -56,6 +54,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { private val topComponent = ComponentName(mContext, TakeScreenshotExecutorTest::class.java) private val testScope = TestScope(UnconfinedTestDispatcher()) private val eventLogger = UiEventLoggerFake() + private val headlessHandler = mock<HeadlessScreenshotHandler>() private val screenshotExecutor = TakeScreenshotExecutorImpl( @@ -64,14 +63,13 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope, requestProcessor, eventLogger, - notificationControllerFactory + notificationControllerFactory, + headlessHandler, ) @Before fun setUp() { - whenever(controllerFactory.create(any(), any())).thenAnswer { - if (it.getArgument<Display>(0).displayId == 0) controller0 else controller1 - } + whenever(controllerFactory.create(any(), any())).thenReturn(controller) whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0) whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1) } @@ -86,14 +84,14 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) verify(controllerFactory).create(eq(internalDisplay), any()) - verify(controllerFactory).create(eq(externalDisplay), any()) + verify(controllerFactory, never()).create(eq(externalDisplay), any()) val capturer = ArgumentCaptor<ScreenshotData>() - verify(controller0).handleScreenshot(capturer.capture(), any(), any()) + verify(controller).handleScreenshot(capturer.capture(), any(), any()) assertThat(capturer.value.displayId).isEqualTo(0) // OnSaved callback should be different. - verify(controller1).handleScreenshot(capturer.capture(), any(), any()) + verify(headlessHandler).handleScreenshot(capturer.capture(), any(), any()) assertThat(capturer.value.displayId).isEqualTo(1) assertThat(eventLogger.numLogs()).isEqualTo(2) @@ -125,10 +123,10 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val capturer = ArgumentCaptor<ScreenshotData>() - verify(controller0).handleScreenshot(capturer.capture(), any(), any()) + verify(controller).handleScreenshot(capturer.capture(), any(), any()) assertThat(capturer.value.displayId).isEqualTo(0) // OnSaved callback should be different. - verify(controller1, never()).handleScreenshot(any(), any(), any()) + verify(headlessHandler, never()).handleScreenshot(any(), any(), any()) assertThat(eventLogger.numLogs()).isEqualTo(1) assertThat(eventLogger.get(0).eventId) @@ -146,13 +144,14 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) verifyNoMoreInteractions(controllerFactory) + verify(headlessHandler, never()).handleScreenshot(any(), any(), any()) screenshotExecutor.onDestroy() } @Test fun executeScreenshots_allowedTypes_allCaptured() = testScope.runTest { - whenever(controllerFactory.create(any(), any())).thenReturn(controller0) + whenever(controllerFactory.create(any(), any())).thenReturn(controller) setDisplays( display(TYPE_INTERNAL, id = 0), @@ -163,7 +162,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) - verify(controller0, times(4)).handleScreenshot(any(), any(), any()) + verify(controller, times(1)).handleScreenshot(any(), any(), any()) + verify(headlessHandler, times(3)).handleScreenshot(any(), any(), any()) screenshotExecutor.onDestroy() } @@ -177,8 +177,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() - verify(controller0).handleScreenshot(any(), any(), capturer0.capture()) - verify(controller1).handleScreenshot(any(), any(), capturer1.capture()) + verify(controller).handleScreenshot(any(), any(), capturer0.capture()) + verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture()) verify(callback, never()).onFinish() @@ -202,8 +202,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() - verify(controller0).handleScreenshot(any(), any(), capturer0.capture()) - verify(controller1).handleScreenshot(any(), nullable(), capturer1.capture()) + verify(controller).handleScreenshot(any(), any(), capturer0.capture()) + verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture()) verify(callback, never()).onFinish() @@ -229,8 +229,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() - verify(controller0).handleScreenshot(any(), any(), capturer0.capture()) - verify(controller1).handleScreenshot(any(), any(), capturer1.capture()) + verify(controller).handleScreenshot(any(), any(), capturer0.capture()) + verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture()) verify(callback, never()).onFinish() @@ -254,50 +254,45 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onDestroy() - verify(controller0).onDestroy() - verify(controller1).onDestroy() + verify(controller).onDestroy() } @Test - fun removeWindows_propagatedToControllers() = + fun removeWindows_propagatedToController() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.removeWindows() - verify(controller0).removeWindow() - verify(controller1).removeWindow() + verify(controller).removeWindow() screenshotExecutor.onDestroy() } @Test - fun onCloseSystemDialogsReceived_propagatedToControllers() = + fun onCloseSystemDialogsReceived_propagatedToController() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onCloseSystemDialogsReceived() - verify(controller0).requestDismissal(any()) - verify(controller1).requestDismissal(any()) + verify(controller).requestDismissal(any()) screenshotExecutor.onDestroy() } @Test - fun onCloseSystemDialogsReceived_someControllerHavePendingTransitions() = + fun onCloseSystemDialogsReceived_controllerHasPendingTransitions() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - whenever(controller0.isPendingSharedTransition).thenReturn(true) - whenever(controller1.isPendingSharedTransition).thenReturn(false) + whenever(controller.isPendingSharedTransition).thenReturn(true) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onCloseSystemDialogsReceived() - verify(controller0, never()).requestDismissal(any()) - verify(controller1).requestDismissal(any()) + verify(controller, never()).requestDismissal(any()) screenshotExecutor.onDestroy() } @@ -317,7 +312,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { .isEqualTo(ScreenshotData.fromRequest(screenshotRequest)) val capturer = ArgumentCaptor<ScreenshotData>() - verify(controller0).handleScreenshot(capturer.capture(), any(), any()) + verify(controller).handleScreenshot(capturer.capture(), any(), any()) assertThat(capturer.value).isEqualTo(toBeReturnedByProcessor) screenshotExecutor.onDestroy() @@ -388,9 +383,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } - whenever(controller0.handleScreenshot(any(), any(), any())) + whenever(controller.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) - whenever(controller1.handleScreenshot(any(), any(), any())) + whenever(headlessHandler.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -408,9 +403,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } - whenever(controller0.handleScreenshot(any(), any(), any())) + whenever(controller.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) - whenever(controller1.handleScreenshot(any(), any(), any())) + whenever(headlessHandler.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -428,9 +423,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } - whenever(controller0.handleScreenshot(any(), any(), any())) + whenever(controller.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) - whenever(controller1.handleScreenshot(any(), any(), any())) + whenever(headlessHandler.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -449,7 +444,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { assertThat(it).isNull() onSavedCallCount += 1 } - whenever(controller0.handleScreenshot(any(), any(), any())).thenAnswer { + whenever(controller.handleScreenshot(any(), any(), any())).thenAnswer { (it.getArgument(1) as Consumer<Uri?>).accept(null) } @@ -478,6 +473,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { var processed: ScreenshotData? = null var toReturn: ScreenshotData? = null var shouldThrowException = false + override suspend fun process(screenshot: ScreenshotData): ScreenshotData { if (shouldThrowException) throw RequestProcessorException("") processed = screenshot diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java index ebdc49f75558..7d2d7c452426 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java @@ -30,12 +30,12 @@ import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; -import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -54,7 +54,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class WorkProfileMessageControllerTest extends SysuiTestCase { private static final String FILES_APP_COMPONENT = "com.android.test/.FilesComponent"; private static final String FILES_APP_LABEL = "Custom Files App"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java index 68a689321219..2981590a3037 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java @@ -17,6 +17,7 @@ package com.android.systemui.screenshot.appclips; import static android.app.Activity.RESULT_OK; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED; import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_CANCELLED; @@ -25,30 +26,45 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityTaskManager.RootTaskInfo; +import android.app.IActivityTaskManager; +import android.app.assist.AssistContent; +import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ApplicationInfoFlags; +import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Process; +import android.os.RemoteException; import android.os.ResultReceiver; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.view.Display; +import android.view.View; import android.widget.ImageView; +import android.widget.TextView; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.intercepting.SingleActivityFactory; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.res.R; +import com.android.systemui.screenshot.AssistContentRequester; import com.android.systemui.screenshot.ImageExporter; import com.android.systemui.settings.UserTracker; @@ -60,9 +76,11 @@ import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.UUID; import java.util.concurrent.Executor; import java.util.function.BiConsumer; @@ -75,14 +93,27 @@ public final class AppClipsActivityTest extends SysuiTestCase { private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888); private static final String TEST_URI_STRING = "www.test-uri.com"; private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING); - private static final BiConsumer<Integer, Bundle> FAKE_CONSUMER = (unUsed1, unUsed2) -> {}; + private static final BiConsumer<Integer, Bundle> FAKE_CONSUMER = (unUsed1, unUsed2) -> { + }; private static final String TEST_CALLING_PACKAGE = "test-calling-package"; + private static final int BACKLINKS_TASK_ID = 42; + private static final String BACKLINKS_TASK_APP_NAME = "Backlinks app"; + private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName"; + private static final RootTaskInfo TASK_THAT_SUPPORTS_BACKLINKS = + createTaskInfoForBacklinksTask(); + + private static final AssistContent ASSIST_CONTENT_FOR_BACKLINKS_TASK = + createAssistContentForBacklinksTask(); @Mock private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper; @Mock private ImageExporter mImageExporter; @Mock + private IActivityTaskManager mAtmService; + @Mock + private AssistContentRequester mAssistContentRequester; + @Mock private PackageManager mPackageManager; @Mock private UserTracker mUserTracker; @@ -98,9 +129,10 @@ public final class AppClipsActivityTest extends SysuiTestCase { protected AppClipsActivityTestable create(Intent unUsed) { return new AppClipsActivityTestable( new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper, - mImageExporter, getContext().getMainExecutor(), - directExecutor()), mPackageManager, mUserTracker, - mUiEventLogger); + mImageExporter, mAtmService, mAssistContentRequester, + mPackageManager, getContext().getMainExecutor(), + directExecutor()), + mPackageManager, mUserTracker, mUiEventLogger); } }; @@ -118,7 +150,7 @@ public final class AppClipsActivityTest extends SysuiTestCase { when(mPackageManager.getApplicationInfoAsUser(eq(TEST_CALLING_PACKAGE), any(ApplicationInfoFlags.class), eq(TEST_USER_ID))).thenReturn(applicationInfo); - when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(TEST_BITMAP); + when(mAppClipsCrossProcessHelper.takeScreenshot(anyInt())).thenReturn(TEST_BITMAP); ImageExporter.Result result = new ImageExporter.Result(); result.uri = TEST_URI; when(mImageExporter.export(any(Executor.class), any(UUID.class), any(Bitmap.class), @@ -132,10 +164,13 @@ public final class AppClipsActivityTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS) public void appClipsLaunched_screenshotDisplayed() { launchActivity(); assertThat(((ImageView) mActivity.findViewById(R.id.preview)).getDrawable()).isNotNull(); + assertThat(mActivity.findViewById(R.id.backlinks_data).getVisibility()) + .isEqualTo(View.GONE); } @Test @@ -176,6 +211,32 @@ public final class AppClipsActivityTest extends SysuiTestCase { verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_CANCELLED, TEST_UID, TEST_CALLING_PACKAGE); } + @Test + @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS) + public void appClipsLaunched_backlinks_displayed() throws RemoteException { + // Set up mocking to verify backlinks view is displayed on screen. + ArgumentCaptor<Integer> displayIdCaptor = ArgumentCaptor.forClass(Integer.class); + when(mAtmService.getAllRootTaskInfosOnDisplay(displayIdCaptor.capture())) + .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS)); + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(ASSIST_CONTENT_FOR_BACKLINKS_TASK); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + when(mPackageManager + .resolveActivity(any(Intent.class), anyInt())) + .thenReturn(createBacklinksTaskResolveInfo()); + + launchActivity(); + waitForIdleSync(); + + assertThat(displayIdCaptor.getValue()).isEqualTo(mActivity.getDisplayId()); + TextView backlinksData = mActivity.findViewById(R.id.backlinks_data); + assertThat(backlinksData.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(backlinksData.getText().toString()).isEqualTo( + mActivity.getString(R.string.backlinks_string, BACKLINKS_TASK_APP_NAME)); + } + private void launchActivity() { launchActivity(createResultReceiver(FAKE_CONSUMER)); } @@ -203,7 +264,7 @@ public final class AppClipsActivityTest extends SysuiTestCase { testReceiver.writeToParcel(parcel, 0); parcel.setDataPosition(0); - testReceiver = ResultReceiver.CREATOR.createFromParcel(parcel); + testReceiver = ResultReceiver.CREATOR.createFromParcel(parcel); parcel.recycle(); return testReceiver; } @@ -212,6 +273,37 @@ public final class AppClipsActivityTest extends SysuiTestCase { mContext.getMainExecutor().execute(runnable); } + private static ResolveInfo createBacklinksTaskResolveInfo() { + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.applicationInfo = new ApplicationInfo(); + activityInfo.name = BACKLINKS_TASK_APP_NAME; + activityInfo.packageName = BACKLINKS_TASK_PACKAGE_NAME; + activityInfo.applicationInfo.packageName = BACKLINKS_TASK_PACKAGE_NAME; + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = activityInfo; + return resolveInfo; + } + + private static RootTaskInfo createTaskInfoForBacklinksTask() { + RootTaskInfo taskInfo = new RootTaskInfo(); + taskInfo.taskId = BACKLINKS_TASK_ID; + taskInfo.isVisible = true; + taskInfo.isRunning = true; + taskInfo.numActivities = 1; + taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass"); + taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo; + taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity); + taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1}; + taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); + return taskInfo; + } + + private static AssistContent createAssistContentForBacklinksTask() { + AssistContent content = new AssistContent(); + content.setWebUri(Uri.parse("https://developers.android.com")); + return content; + } + public static class AppClipsActivityTestable extends AppClipsActivity { public AppClipsActivityTestable(AppClipsViewModel.Factory viewModelFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java index ad0797c88f62..dcb75d1e187b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java @@ -16,28 +16,51 @@ package com.android.systemui.screenshot.appclips; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT; +import static android.content.ClipDescription.MIMETYPE_TEXT_URILIST; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.ACTION_VIEW; import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED; +import static android.content.Intent.CATEGORY_LAUNCHER; +import static android.view.Display.DEFAULT_DISPLAY; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityTaskManager.RootTaskInfo; +import android.app.IActivityTaskManager; +import android.app.assist.AssistContent; +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.net.Uri; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; -import android.view.Display; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; +import com.android.systemui.screenshot.AssistContentRequester; import com.android.systemui.screenshot.ImageExporter; import com.google.common.util.concurrent.Futures; @@ -45,9 +68,13 @@ import com.google.common.util.concurrent.Futures; 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; +import java.util.Collections; +import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -60,27 +87,44 @@ public final class AppClipsViewModelTest extends SysuiTestCase { private static final Rect FAKE_RECT = new Rect(); private static final Uri FAKE_URI = Uri.parse("www.test-uri.com"); private static final UserHandle USER_HANDLE = Process.myUserHandle(); + private static final int BACKLINKS_TASK_ID = 42; + private static final String BACKLINKS_TASK_APP_NAME = "Ultimate question app"; + private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName"; + private static final AssistContent EMPTY_ASSIST_CONTENT = new AssistContent(); @Mock private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper; @Mock private ImageExporter mImageExporter; + @Mock private IActivityTaskManager mAtmService; + @Mock private AssistContentRequester mAssistContentRequester; + @Mock + private PackageManager mPackageManager; + private ArgumentCaptor<Intent> mPackageManagerIntentCaptor; private AppClipsViewModel mViewModel; @Before - public void setUp() { + public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); + mPackageManagerIntentCaptor = ArgumentCaptor.forClass(Intent.class); + + // Set up mocking for backlinks. + when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY)) + .thenReturn(List.of(createTaskInfoForBacklinksTask())); + when(mPackageManager.resolveActivity(mPackageManagerIntentCaptor.capture(), anyInt())) + .thenReturn(createBacklinksTaskResolveInfo()); mViewModel = new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper, mImageExporter, + mAtmService, mAssistContentRequester, mPackageManager, getContext().getMainExecutor(), directExecutor()).create(AppClipsViewModel.class); } @Test public void performScreenshot_fails_shouldUpdateErrorWithFailed() { - when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(null); + when(mAppClipsCrossProcessHelper.takeScreenshot(anyInt())).thenReturn(null); - mViewModel.performScreenshot(); + mViewModel.performScreenshot(DEFAULT_DISPLAY); waitForIdleSync(); - verify(mAppClipsCrossProcessHelper).takeScreenshot(); + verify(mAppClipsCrossProcessHelper).takeScreenshot(DEFAULT_DISPLAY); assertThat(mViewModel.getErrorLiveData().getValue()) .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED); assertThat(mViewModel.getResultLiveData().getValue()).isNull(); @@ -88,12 +132,12 @@ public final class AppClipsViewModelTest extends SysuiTestCase { @Test public void performScreenshot_succeeds_shouldUpdateScreenshotWithBitmap() { - when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(FAKE_BITMAP); + when(mAppClipsCrossProcessHelper.takeScreenshot(DEFAULT_DISPLAY)).thenReturn(FAKE_BITMAP); - mViewModel.performScreenshot(); + mViewModel.performScreenshot(DEFAULT_DISPLAY); waitForIdleSync(); - verify(mAppClipsCrossProcessHelper).takeScreenshot(); + verify(mAppClipsCrossProcessHelper).takeScreenshot(DEFAULT_DISPLAY); assertThat(mViewModel.getErrorLiveData().getValue()).isNull(); assertThat(mViewModel.getScreenshot().getValue()).isEqualTo(FAKE_BITMAP); } @@ -101,7 +145,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { @Test public void saveScreenshot_throwsError_shouldUpdateErrorWithFailed() { when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), eq(USER_HANDLE), - eq(Display.DEFAULT_DISPLAY))).thenReturn( + eq(DEFAULT_DISPLAY))).thenReturn( Futures.immediateFailedFuture(new ExecutionException(new Throwable()))); mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE); @@ -115,7 +159,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { @Test public void saveScreenshot_failsSilently_shouldUpdateErrorWithFailed() { when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), eq(USER_HANDLE), - eq(Display.DEFAULT_DISPLAY))).thenReturn( + eq(DEFAULT_DISPLAY))).thenReturn( Futures.immediateFuture(new ImageExporter.Result())); mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE); @@ -131,7 +175,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase { ImageExporter.Result result = new ImageExporter.Result(); result.uri = FAKE_URI; when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), eq(USER_HANDLE), - eq(Display.DEFAULT_DISPLAY))).thenReturn(Futures.immediateFuture(result)); + eq(DEFAULT_DISPLAY))).thenReturn(Futures.immediateFuture(result)); mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE); waitForIdleSync(); @@ -139,4 +183,198 @@ public final class AppClipsViewModelTest extends SysuiTestCase { assertThat(mViewModel.getErrorLiveData().getValue()).isNull(); assertThat(mViewModel.getResultLiveData().getValue()).isEqualTo(FAKE_URI); } + + @Test + public void triggerBacklinks_shouldUpdateBacklinks_withUri() { + Uri expectedUri = Uri.parse("https://developers.android.com"); + AssistContent contentWithUri = new AssistContent(); + contentWithUri.setWebUri(expectedUri); + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(contentWithUri); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + + mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); + waitForIdleSync(); + + Intent queriedIntent = mPackageManagerIntentCaptor.getValue(); + assertThat(queriedIntent.getData()).isEqualTo(expectedUri); + assertThat(queriedIntent.getAction()).isEqualTo(ACTION_VIEW); + + ClipData result = mViewModel.getBacklinksLiveData().getValue(); + ClipDescription resultDescription = result.getDescription(); + assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME); + assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_URILIST); + assertThat(result.getItemCount()).isEqualTo(1); + assertThat(result.getItemAt(0).getUri()).isEqualTo(expectedUri); + } + + @Test + public void triggerBacklinks_withNonResolvableUri_usesMainLauncherIntent() { + Uri expectedUri = Uri.parse("https://developers.android.com"); + AssistContent contentWithUri = new AssistContent(); + contentWithUri.setWebUri(expectedUri); + resetPackageManagerMockingForUsingFallbackBacklinks(); + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(contentWithUri); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + + mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); + waitForIdleSync(); + + verifyMainLauncherBacklinksIntent(); + } + + @Test + public void triggerBacklinks_shouldUpdateBacklinks_withAppProvidedIntent() { + Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME); + AssistContent contentWithAppProvidedIntent = new AssistContent(); + contentWithAppProvidedIntent.setIntent(expectedIntent); + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(contentWithAppProvidedIntent); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + + mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); + waitForIdleSync(); + + Intent queriedIntent = mPackageManagerIntentCaptor.getValue(); + assertThat(queriedIntent.getPackage()).isEqualTo(expectedIntent.getPackage()); + + ClipData result = mViewModel.getBacklinksLiveData().getValue(); + ClipDescription resultDescription = result.getDescription(); + assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME); + assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_INTENT); + assertThat(result.getItemCount()).isEqualTo(1); + assertThat(result.getItemAt(0).getIntent()).isEqualTo(expectedIntent); + } + + @Test + public void triggerBacklinks_withNonResolvableAppProvidedIntent_usesMainLauncherIntent() { + Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME); + AssistContent contentWithAppProvidedIntent = new AssistContent(); + contentWithAppProvidedIntent.setIntent(expectedIntent); + resetPackageManagerMockingForUsingFallbackBacklinks(); + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(contentWithAppProvidedIntent); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + + mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); + waitForIdleSync(); + + verifyMainLauncherBacklinksIntent(); + } + + @Test + public void triggerBacklinks_shouldUpdateBacklinks_withMainLauncherIntent() { + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + + mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); + waitForIdleSync(); + + Intent queriedIntent = mPackageManagerIntentCaptor.getValue(); + assertThat(queriedIntent.getPackage()).isEqualTo(BACKLINKS_TASK_PACKAGE_NAME); + assertThat(queriedIntent.getAction()).isEqualTo(ACTION_MAIN); + assertThat(queriedIntent.getCategories()).containsExactly(CATEGORY_LAUNCHER); + + verifyMainLauncherBacklinksIntent(); + } + + @Test + public void triggerBacklinks_withNonResolvableMainLauncherIntent_noBacklinksAvailable() { + reset(mPackageManager); + doAnswer(invocation -> { + AssistContentRequester.Callback callback = invocation.getArgument(1); + callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT); + return null; + }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any()); + + mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); + waitForIdleSync(); + + assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull(); + } + + @Test + public void triggerBacklinks_nonStandardActivityIgnored_noBacklinkAvailable() + throws RemoteException { + reset(mAtmService); + RootTaskInfo taskInfo = createTaskInfoForBacklinksTask(); + taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME); + when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY)) + .thenReturn(List.of(taskInfo)); + + mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY); + waitForIdleSync(); + + assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull(); + } + + @Test + public void triggerBacklinks_taskIdsToIgnoreConsidered_noBacklinkAvailable() { + mViewModel.triggerBacklinks(Set.of(BACKLINKS_TASK_ID), DEFAULT_DISPLAY); + waitForIdleSync(); + + assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull(); + } + + private void resetPackageManagerMockingForUsingFallbackBacklinks() { + reset(mPackageManager); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + // First the logic queries whether a package has a launcher activity, this should + // resolve otherwise the logic filters out the task. + .thenReturn(createBacklinksTaskResolveInfo()) + // Then logic queries with the backlinks intent, this should not resolve for the + // logic to use the fallback intent. + .thenReturn(null); + } + + private void verifyMainLauncherBacklinksIntent() { + ClipData result = mViewModel.getBacklinksLiveData().getValue(); + assertThat(result.getItemCount()).isEqualTo(1); + + ClipDescription resultDescription = result.getDescription(); + assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME); + assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_INTENT); + + Intent actualBacklinksIntent = result.getItemAt(0).getIntent(); + assertThat(actualBacklinksIntent.getPackage()).isEqualTo(BACKLINKS_TASK_PACKAGE_NAME); + assertThat(actualBacklinksIntent.getAction()).isEqualTo(ACTION_MAIN); + assertThat(actualBacklinksIntent.getCategories()).containsExactly(CATEGORY_LAUNCHER); + } + + private static ResolveInfo createBacklinksTaskResolveInfo() { + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.applicationInfo = new ApplicationInfo(); + activityInfo.name = BACKLINKS_TASK_APP_NAME; + activityInfo.packageName = BACKLINKS_TASK_PACKAGE_NAME; + activityInfo.applicationInfo.packageName = BACKLINKS_TASK_PACKAGE_NAME; + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = activityInfo; + return resolveInfo; + } + + private static RootTaskInfo createTaskInfoForBacklinksTask() { + RootTaskInfo taskInfo = new RootTaskInfo(); + taskInfo.taskId = BACKLINKS_TASK_ID; + taskInfo.isVisible = true; + taskInfo.isRunning = true; + taskInfo.numActivities = 1; + taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass"); + taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo; + taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity); + taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1}; + taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); + return taskInfo; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt index ce2facd59482..6b9865fbbacc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.screenshot.message import android.content.ComponentName +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.content.pm.PackageManager import android.graphics.Canvas import android.graphics.ColorFilter @@ -27,7 +28,9 @@ import com.android.systemui.screenshot.data.repository.ProfileTypeRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class ProfileMessageControllerTest { @Test fun personalScreenshot() = runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt index 4945ace3b88c..3b0e194ce223 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.screenshot.policy import android.content.ComponentName +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.graphics.Insets import android.graphics.Rect import android.os.UserHandle @@ -34,7 +35,9 @@ import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class PolicyRequestProcessorTest { val imageCapture = object : ImageCapture { @@ -78,4 +81,4 @@ class PolicyRequestProcessorTest { assertWithMessage("The topComponent of the screenshot").that(result.topComponent) .isEqualTo(ComponentName.unflattenFromString(FILES)) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt index 9e3ae054d31b..6e57761aafa6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.screenshot.policy import android.content.ComponentName +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.os.UserHandle import com.android.systemui.kosmos.Kosmos import com.android.systemui.screenshot.data.model.DisplayContentModel @@ -40,7 +41,9 @@ import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class PrivateProfilePolicyTest { private val kosmos = Kosmos() private val policy = PrivateProfilePolicy(kosmos.profileTypeRepository) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt index 5d35528b0cf0..8d217fc3ad25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.screenshot.policy import android.content.ComponentName +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags @@ -50,7 +51,9 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class WorkProfilePolicyTest { @JvmField @Rule val setFlagsRule = SetFlagsRule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java index aad461392cf6..88cb00cda638 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java @@ -23,8 +23,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -39,7 +39,7 @@ import org.junit.runner.RunWith; * client/connection. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class FakeSessionTest extends SysuiTestCase { @Test public void testNonEmptyResult_hasImage() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java index f39f5439d4ac..f8de714c6021 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java @@ -29,9 +29,9 @@ import static org.mockito.Mockito.when; import static java.lang.Math.abs; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.view.ScrollCaptureResponse; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.testing.UiEventLoggerFake; @@ -46,7 +46,7 @@ import org.junit.runner.RunWith; * screenshots. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ScrollCaptureControllerTest extends SysuiTestCase { private static final ScrollCaptureResponse EMPTY_RESPONSE = diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt index e32086b79918..e39e0fbad6e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt @@ -17,12 +17,15 @@ package com.android.systemui.screenshot.ui.viewmodel import android.view.accessibility.AccessibilityManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.mock @SmallTest +@RunWith(AndroidJUnit4::class) class ScreenshotViewModelTest { private val accessibilityManager: AccessibilityManager = mock(AccessibilityManager::class.java) private val appearance = ActionButtonAppearance(null, "Label", "Description") diff --git a/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java index a345f7804ae9..ca8da635e0f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java @@ -22,12 +22,12 @@ import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; @@ -38,7 +38,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class ScrimViewTest extends LeakCheckedTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt index 333e6344c18f..1a4749c3196c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.sensorprivacy -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock @@ -11,7 +11,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class SensorUseStartedActivityTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt index 2b7840533df2..98260d88f389 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.settings.brightness import android.hardware.display.DisplayManager import android.os.Handler import android.service.vr.IVrManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.settings.DisplayTracker @@ -40,7 +40,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class BrightnessControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 31acd86f52f4..a06784e257de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -18,11 +18,11 @@ package com.android.systemui.settings.brightness import android.content.Intent import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule @@ -55,7 +55,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class BrightnessDialogTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt index 2b3588aa6e3c..c7bea5911a15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.shade -import android.testing.AndroidTestingRunner import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -33,7 +33,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { private lateinit var qqsConstraint: ConstraintSet diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt index 9b2e085560a1..8c8083543823 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.shade -import android.testing.AndroidTestingRunner import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock @@ -27,7 +27,7 @@ import org.mockito.Mockito.inOrder import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ConstraintChangeTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt index 0abb08427478..05c17060b461 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.shade -import android.testing.AndroidTestingRunner import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock @@ -26,7 +26,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.inOrder @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ConstraintChangesTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt index b4f890b5315e..2ac0ed0efff4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.shade import android.os.PowerManager -import android.testing.AndroidTestingRunner import android.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor @@ -47,7 +47,7 @@ import org.mockito.MockitoAnnotations @SmallTest @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() { @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var falsingCollector: FalsingCollector diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt index bff408a7ddba..abc96ee3c5f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.shade -import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -46,7 +46,7 @@ import org.mockito.MockitoAnnotations * the set of ids, which also dictact which direction to move and when, via a filter fn. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationPanelUnfoldAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index c3cedf84a864..88b832d15342 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -438,6 +438,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFakeKeyguardRepository, mKeyguardTransitionInteractor, mPowerInteractor, + mShadeRepository, new FakeUserSetupRepository(), mock(UserSwitcherInteractor.class), new ShadeInteractorLegacyImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 65364053f109..90e8ea5f34c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -18,7 +18,6 @@ package com.android.systemui.shade; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; -import static com.android.systemui.Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING; @@ -49,13 +48,13 @@ import android.graphics.Point; import android.os.PowerManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import androidx.constraintlayout.widget.ConstraintSet; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.DejankUtils; @@ -80,7 +79,7 @@ import org.mockito.InOrder; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class NotificationPanelViewControllerTest extends NotificationPanelViewControllerBaseTest { @@ -679,32 +678,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test - @EnableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX) - public void testCanBeCollapsed_expandedInKeyguard() { - mStatusBarStateController.setState(KEYGUARD); - mNotificationPanelViewController.setExpandedFraction(1f); - - assertThat(mNotificationPanelViewController.canBeCollapsed()).isFalse(); - } - - @Test - @EnableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX) - public void testCanBeCollapsed_expandedInShade() { - mStatusBarStateController.setState(SHADE); - mNotificationPanelViewController.setExpandedFraction(1f); - assertThat(mNotificationPanelViewController.canBeCollapsed()).isTrue(); - } - - @Test - @DisableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX) - public void testCanBeCollapsed_expandedInKeyguard_flagDisabled() { - mStatusBarStateController.setState(KEYGUARD); - mNotificationPanelViewController.setExpandedFraction(1f); - - assertThat(mNotificationPanelViewController.canBeCollapsed()).isTrue(); - } - - @Test @Ignore("b/341163515 - fails to clean up animators correctly") public void testSwipeWhileLocked_notifiesKeyguardState() { mStatusBarStateController.setState(KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt index d47bd4775f12..e1d92e780c2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt @@ -19,11 +19,11 @@ package com.android.systemui.shade import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.HapticFeedbackConstants import android.view.View import android.view.ViewStub +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.CollectionUtils import com.android.keyguard.KeyguardClockSwitch.LARGE @@ -55,7 +55,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class NotificationPanelViewControllerWithCoroutinesTest : diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 31bd12f9a168..fec7424ac006 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -16,10 +16,10 @@ package com.android.systemui.shade import android.os.SystemClock -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.MotionEvent import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.LegacyLockIconViewController @@ -82,7 +82,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class NotificationShadeWindowViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt index 0c3af03da59e..97ce37c82c83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.shade -import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.constraintlayout.widget.ConstraintLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.qs.QSFragmentLegacy @@ -34,7 +34,7 @@ import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationsQuickSettingsContainerTest : SysuiTestCase() { @Mock private lateinit var qsFrame: View diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt index 3900defd4ed4..effd28edc68b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt @@ -20,9 +20,9 @@ import android.hardware.display.AmbientDisplayConfiguration import android.os.PowerManager import android.provider.Settings.Secure.DOZE_DOUBLE_TAP_GESTURE import android.provider.Settings.Secure.DOZE_TAP_SCREEN_GESTURE -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dock.DockManager @@ -49,7 +49,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class PulsingGestureListenerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt index 2cd740d35c14..f38bf13d0bda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt @@ -3,8 +3,8 @@ package com.android.systemui.shade import android.content.Context import android.content.res.Resources import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.view.DisplayCutout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -21,7 +21,7 @@ import org.mockito.Mock import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class QsBatteryModeControllerTest : SysuiTestCase() { private companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index 85541aa8abda..06a883c02ac0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -218,6 +218,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { mKeyguardRepository, keyguardTransitionInteractor, powerInteractor, + mShadeRepository, new FakeUserSetupRepository(), mUserSwitcherInteractor, new ShadeInteractorLegacyImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java index ad4b4fdc4eec..9a6423d96d4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java @@ -36,10 +36,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.plugins.qs.QS; @@ -53,7 +53,7 @@ import org.mockito.ArgumentCaptor; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class QuickSettingsControllerImplTest extends QuickSettingsControllerImplBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt index dfd7a715fcdf..46961d4db0f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.app.StatusBarManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.google.common.truth.Truth.assertThat @@ -25,9 +26,11 @@ import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImplBaseTest() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 6bdd3ef5cebe..02172383485d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -16,10 +16,10 @@ package com.android.systemui.shade -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Display import android.view.WindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase @@ -59,7 +59,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class ShadeControllerImplTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt index 89ae42fd9f83..4734edee7626 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.shade +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class ShadeExpansionStateManagerTest : SysuiTestCase() { private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt index 7a9ef62278a4..c6699590e499 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.shade.carrier -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Assert.assertNotSame @@ -24,7 +24,7 @@ import org.junit.Assert.assertSame import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class CellSignalStateTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java index a42121a41e46..1a2531ac8c75 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java @@ -21,13 +21,13 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.mobile.TelephonyIcons; @@ -38,7 +38,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class ShadeCarrierTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index 750693c483a2..97acc6e53cdf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -40,7 +40,7 @@ class ShadeRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { - underTest = ShadeRepositoryImpl() + underTest = ShadeRepositoryImpl(getContext()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt index 7c33648e08a3..10c017bdc525 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt @@ -1,6 +1,6 @@ package com.android.systemui.shade.transition -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository @@ -30,7 +30,7 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ScrimShadeTransitionControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt index 4679a58645d9..89a3d5b5cf0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt @@ -20,8 +20,8 @@ import android.content.Context import android.content.res.Resources import android.content.res.TypedArray import android.platform.test.annotations.PlatinumTest -import android.testing.AndroidTestingRunner import android.util.AttributeSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.shared.R @@ -39,7 +39,7 @@ import org.mockito.junit.MockitoRule @PlatinumTest(focusArea = "sysui") @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DoubleShadowTextClockTest : SysuiTestCase() { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt index fc230e362735..5d8f868158e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.shared.animation import android.graphics.Paint -import android.testing.AndroidTestingRunner import android.widget.FrameLayout import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DisableSubpixelTextTransitionListenerTest : SysuiTestCase() { private lateinit var disableSubpixelTextTransitionListener: diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt index 293dc048af1d..3a53a5aaedfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt @@ -14,9 +14,9 @@ */ package com.android.systemui.shared.animation -import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction @@ -34,7 +34,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() { private val progressProvider = FakeUnfoldTransitionProvider() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt index 3cb48d9b2462..fbaddfaf7216 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt @@ -15,8 +15,8 @@ package com.android.systemui.shared.animation import android.graphics.Point +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.view.Display import android.view.Surface.ROTATION_0 import android.view.Surface.ROTATION_90 @@ -36,7 +36,7 @@ import org.mockito.Mockito.spy import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt index 7b0cd19ca7f2..e076418f2630 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.shared.clocks -import android.testing.AndroidTestingRunner import android.view.LayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.app.animation.Interpolators import com.android.systemui.customization.R @@ -34,7 +34,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class AnimatableClockViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 74d017375eb5..2f52248dab6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -19,7 +19,7 @@ import android.content.ComponentName import android.content.ContentResolver import android.content.Context import android.graphics.drawable.Drawable -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags @@ -55,7 +55,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ClockRegistryTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt index e0e8d1f51884..2522ed7f9a3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.shared.clocks import android.content.res.Resources import android.graphics.Color import android.graphics.drawable.Drawable -import android.testing.AndroidTestingRunner import android.util.TypedValue import android.view.LayoutInflater import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.customization.R @@ -53,7 +53,7 @@ import org.mockito.junit.MockitoJUnit private fun DefaultClockProvider.createClock(id: ClockId): DefaultClockController = createClock(ClockSettings(id, null)) as DefaultClockController -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DefaultClockProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt index 1a0e932d7e8a..8418598c256b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.shared.condition -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.shared.condition.Condition.START_EAGERLY @@ -32,7 +32,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class CombinedConditionTest : SysuiTestCase() { class FakeCondition diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt index 0a8210d6b319..83fb14aaf792 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt @@ -1,6 +1,6 @@ package com.android.systemui.shared.condition -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -19,7 +19,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ConditionExtensionsTest : SysuiTestCase() { private lateinit var testScope: TestScope diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java index 65ca0a2400c1..267f22bb2569 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java @@ -27,8 +27,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -51,7 +51,7 @@ import java.util.Collections; import java.util.HashSet; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ConditionMonitorTest extends SysuiTestCase { private FakeCondition mCondition1; private FakeCondition mCondition2; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java index cec5d0a3b55f..a224843e592a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java @@ -25,8 +25,8 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ConditionTest extends SysuiTestCase { @Mock CoroutineScope mScope; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt index 5fc09c7d5563..2a6754cbdd1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt @@ -17,12 +17,12 @@ package com.android.systemui.shared.navigationbar import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl import android.view.View import android.view.ViewRootImpl import androidx.concurrent.futures.DirectExecutor +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -43,7 +43,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @RunWithLooper class RegionSamplingHelperTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt index 0dd988d424b9..69cc9d5d971a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.shared.notifications.data.repository import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -28,10 +29,9 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class NotificationSettingsRepositoryTest : SysuiTestCase() { private lateinit var underTest: NotificationSettingsRepository @@ -56,7 +56,7 @@ class NotificationSettingsRepositoryTest : SysuiTestCase() { @Test fun testGetIsShowNotificationsOnLockscreenEnabled() = testScope.runTest { - val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled) + val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled()) secureSettingsRepository.setInt( name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, @@ -74,7 +74,7 @@ class NotificationSettingsRepositoryTest : SysuiTestCase() { @Test fun testSetIsShowNotificationsOnLockscreenEnabled() = testScope.runTest { - val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled) + val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled()) underTest.setShowNotificationsOnLockscreenEnabled(true) assertThat(showNotifs).isEqualTo(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java index 40c84d611198..c721ceb10f86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java @@ -28,9 +28,9 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.Uri; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -55,7 +55,7 @@ import java.util.Collections; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class PluginManagerTest extends SysuiTestCase { private static final String PRIVILEGED_PACKAGE = "com.android.systemui"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java index 711187be33fb..15fdc7c84060 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java @@ -17,6 +17,7 @@ package com.android.systemui.shared.plugins; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -29,8 +30,10 @@ import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; @SmallTest +@RunWith(AndroidJUnit4.class) public class VersionInfoTest extends SysuiTestCase { @Rule diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt index 1031621e2e7e..7fc845c17613 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt @@ -4,8 +4,8 @@ import android.app.WallpaperColors import android.app.WallpaperManager import android.graphics.Color import android.graphics.RectF -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any @@ -28,7 +28,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class RegionSamplerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt index ee3d87089b6d..5a4ef055265c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt @@ -15,11 +15,11 @@ */ package com.android.systemui.shared.rotation -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Display import android.view.WindowInsetsController import android.view.WindowManagerPolicyConstants +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -27,7 +27,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @RunWithLooper class RotationButtonControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt index b761647e24e3..d67ce303c451 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.shared.system +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -8,6 +9,7 @@ import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Ignore import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.only @@ -16,6 +18,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class UncaughtExceptionPreHandlerTest : SysuiTestCase() { private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt index e9676c83c39e..d0ba629c4998 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt @@ -20,11 +20,13 @@ import android.content.res.Resources import android.view.CrossWindowBlurListeners import android.view.SurfaceControl import android.view.ViewRootImpl +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.any @@ -37,6 +39,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class BlurUtilsTest : SysuiTestCase() { @Mock lateinit var resources: Resources @@ -117,4 +120,4 @@ class BlurUtilsTest : SysuiTestCase() { return transaction } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 5da3a569f91a..9df46e5047d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -42,6 +42,7 @@ import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; import android.view.accessibility.Flags; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.LetterboxDetails; @@ -54,8 +55,10 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; @SmallTest +@RunWith(AndroidJUnit4.class) public class CommandQueueTest extends SysuiTestCase { private static final LetterboxDetails[] TEST_LETTERBOX_DETAILS = new LetterboxDetails[] { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java index 3b85dbacc699..5f9c9df7ab23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java @@ -23,18 +23,21 @@ import android.app.RemoteInput; import android.content.Intent; import android.graphics.drawable.Icon; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.systemui.SysuiTestCase; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.Arrays; import java.util.Collections; import java.util.List; @SmallTest +@RunWith(AndroidJUnit4.class) public class NotificationUiAdjustmentTest extends SysuiTestCase { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt index 9c59f9bf49d8..396d0171783b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt @@ -20,6 +20,7 @@ import android.telephony.ServiceState import android.telephony.SubscriptionInfo import android.telephony.TelephonyManager import android.telephony.telephonyManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.SysuiTestCase @@ -44,11 +45,13 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class OperatorNameViewControllerTest : SysuiTestCase() { private lateinit var underTest: OperatorNameViewController private lateinit var airplaneModeInteractor: AirplaneModeInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index 6cac30a75c2a..c3e810ed1e31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.res.R +import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository @@ -68,13 +69,13 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test - fun chip_inCall_isShown() = + fun chip_inCall_isShownAsTimer() = testScope.runTest { val latest by collectLastValue(underTest.chip) repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 345, intent = null)) - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) } @Test @@ -91,11 +92,12 @@ class CallChipViewModelTest : SysuiTestCase() { // started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be // relative to elapsedRealtime, so it should be 2000ms before the elapsed realtime set // on the clock. - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(398_000) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs) + .isEqualTo(398_000) } @Test - fun chip_inCall_iconIsPhone() = + fun chip_iconIsPhone() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -106,6 +108,17 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test + fun chip_colorsAreThemed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null)) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo(ColorsModel.Themed) + } + + @Test fun chip_resetsCorrectly() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -115,7 +128,8 @@ class CallChipViewModelTest : SysuiTestCase() { // Start a call repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null)) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(398_000) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs) + .isEqualTo(398_000) // End the call repo.setOngoingCallState(OngoingCallModel.NoCall) @@ -128,20 +142,18 @@ class CallChipViewModelTest : SysuiTestCase() { // Start a new call, which started 1000ms ago repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 102_000, intent = null)) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(499_000) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs) + .isEqualTo(499_000) } @Test - fun chip_inCall_nullIntent_clickListenerDoesNothing() = + fun chip_inCall_nullIntent_nullClickListener() = testScope.runTest { val latest by collectLastValue(underTest.chip) repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null)) - val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener - - clickListener.onClick(chipView) - // Just verify nothing crashes + assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull() } @Test @@ -152,8 +164,9 @@ class CallChipViewModelTest : SysuiTestCase() { val intent = mock<PendingIntent>() repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = intent)) val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener + assertThat(clickListener).isNotNull() - clickListener.onClick(chipView) + clickListener!!.onClick(chipView) verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(intent, null) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt index 93d781f6817e..bde668ece83f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastToO import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.CAST_TO_OTHER_DEVICES_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection +import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.SystemUIDialog @@ -90,7 +91,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test - fun chip_singleTaskState_otherDevicesPackage_isShown() = + fun chip_singleTaskState_otherDevicesPackage_isShownAsTimer() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -100,25 +101,36 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) val icon = (latest as OngoingActivityChipModel.Shown).icon assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected) } @Test - fun chip_entireScreenState_otherDevicesPackage_isShown() = + fun chip_entireScreenState_otherDevicesPackage_isShownAsTimer() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) val icon = (latest as OngoingActivityChipModel.Shown).icon assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected) } @Test + fun chip_colorsAreRed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) + + assertThat((latest as OngoingActivityChipModel.Shown).colors).isEqualTo(ColorsModel.Red) + } + + @Test fun chip_singleTaskState_normalPackage_isHidden() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -150,7 +162,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) @@ -163,7 +175,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { ) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678) } @Test @@ -174,8 +186,9 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + assertThat(clickListener).isNotNull() - clickListener.onClick(chipView) + clickListener!!.onClick(chipView) verify(kosmos.mockDialogTransitionAnimator) .showFromView( eq(mockCastDialog), @@ -197,8 +210,9 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { ) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + assertThat(clickListener).isNotNull() - clickListener.onClick(chipView) + clickListener!!.onClick(chipView) verify(kosmos.mockDialogTransitionAnimator) .showFromView( eq(mockCastDialog), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt index 45044f74e864..8e8b0820110d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.res.R import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate +import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.SystemUIDialog @@ -87,28 +88,84 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test - fun chip_startingState_isHidden() = + fun chip_startingState_isShownAsCountdownWithoutIconOrClickListener() = testScope.runTest { val latest by collectLastValue(underTest.chip) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(400) - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Countdown::class.java) + assertThat((latest as OngoingActivityChipModel.Shown).icon).isNull() + assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull() + } + + // The millis we typically get from [ScreenRecordRepository] are around 2995, 1995, and 995. + @Test + fun chip_startingState_millis2995_is3() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2995) + + assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted) + .isEqualTo(3) + } + + @Test + fun chip_startingState_millis1995_is2() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(1995) + + assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted) + .isEqualTo(2) + } + + @Test + fun chip_startingState_millis995_is1() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(995) + + assertThat((latest as OngoingActivityChipModel.Shown.Countdown).secondsUntilStarted) + .isEqualTo(1) } @Test - fun chip_recordingState_isShownWithIcon() = + fun chip_recordingState_isShownAsTimerWithIcon() = testScope.runTest { val latest by collectLastValue(underTest.chip) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) val icon = (latest as OngoingActivityChipModel.Shown).icon assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenrecord) } @Test + fun chip_startingState_colorsAreRed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2000L) + + assertThat((latest as OngoingActivityChipModel.Shown).colors).isEqualTo(ColorsModel.Red) + } + + @Test + fun chip_recordingState_colorsAreRed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + + assertThat((latest as OngoingActivityChipModel.Shown).colors).isEqualTo(ColorsModel.Red) + } + + @Test fun chip_timeResetsOnEachNewRecording() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -117,7 +174,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234) screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) @@ -126,7 +183,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678) } @Test @@ -137,8 +194,9 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + assertThat(clickListener).isNotNull() - clickListener.onClick(chipView) + clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) .showFromView( @@ -158,8 +216,9 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.EntireScreen("host.package") val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + assertThat(clickListener).isNotNull() - clickListener.onClick(chipView) + clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) .showFromView( @@ -182,8 +241,9 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { ) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + assertThat(clickListener).isNotNull() - clickListener.onClick(chipView) + clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) .showFromView( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt index 4c2546e141fb..63c29ac81b34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt @@ -59,7 +59,7 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() { underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null) - verify(sysuiDialog).setIcon(R.drawable.ic_screenshot_share) + verify(sysuiDialog).setIcon(R.drawable.ic_present_to_all) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt index 9aef526932df..2e5f7f55031c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate +import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.SystemUIDialog @@ -115,7 +116,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { } @Test - fun chip_singleTaskState_normalPackage_isShown() = + fun chip_singleTaskState_normalPackage_isShownAsTimer() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -125,22 +126,33 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) val icon = (latest as OngoingActivityChipModel.Shown).icon - assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share) + assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all) } @Test - fun chip_entireScreenState_normalPackage_isShown() = + fun chip_entireScreenState_normalPackage_isShownAsTimer() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) val icon = (latest as OngoingActivityChipModel.Shown).icon - assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share) + assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all) + } + + @Test + fun chip_colorsAreRed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) + + assertThat((latest as OngoingActivityChipModel.Shown).colors).isEqualTo(ColorsModel.Red) } @Test @@ -153,7 +165,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) @@ -166,7 +178,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { ) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678) + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678) } @Test @@ -177,8 +189,9 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + assertThat(clickListener).isNotNull() - clickListener.onClick(chipView) + clickListener!!.onClick(chipView) verify(kosmos.mockDialogTransitionAnimator) .showFromView( eq(mockShareDialog), @@ -199,8 +212,9 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { ) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + assertThat(clickListener).isNotNull() - clickListener.onClick(chipView) + clickListener!!.onClick(chipView) verify(kosmos.mockDialogTransitionAnimator) .showFromView( eq(mockShareDialog), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt index 7d2b463afab7..5fbdfbf17df4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.chips.ui.view -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R @@ -29,7 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ChipBackgroundContainerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt index b8d4e47544dd..6f7711759603 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.chips.ui.view -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R @@ -36,7 +36,7 @@ private const val LARGE_TEXT = "00:000" private const val XL_TEXT = "00:0000" @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ChipChronometerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index a7b1411f2a45..8bc83cf2f3c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon @@ -34,15 +35,22 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel +import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val testScope = kosmos.testScope + private val systemClock = kosmos.fakeSystemClock private val screenRecordState = kosmos.screenRecordRepository.screenRecordState private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState @@ -188,6 +196,39 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { assertIsCallChip(latest) } + /** Regression test for b/347726238. */ + @Test + fun chip_timerDoesNotResetAfterSubscribersRestart() = + testScope.runTest { + var latest: OngoingActivityChipModel? = null + + val job1 = underTest.chip.onEach { latest = it }.launchIn(this) + + // Start a chip with a timer + systemClock.setElapsedRealtime(1234) + screenRecordState.value = ScreenRecordModel.Recording + + runCurrent() + + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234) + + // Stop subscribing to the chip flow + job1.cancel() + + // Let time pass + systemClock.setElapsedRealtime(5678) + + // WHEN we re-subscribe to the chip flow + val job2 = underTest.chip.onEach { latest = it }.launchIn(this) + + runCurrent() + + // THEN the old start time is still used + assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234) + + job2.cancel() + } + companion object { fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) { assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) @@ -198,7 +239,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { fun assertIsShareToAppChip(latest: OngoingActivityChipModel?) { assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) val icon = (latest as OngoingActivityChipModel.Shown).icon - assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenshot_share) + assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all) } fun assertIsCallChip(latest: OngoingActivityChipModel?) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt index cfbe8e36537d..673cf59fc72b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.commandline +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -24,8 +25,10 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class CommandParserTest : SysuiTestCase() { private val parser = CommandParser() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt index e391d6b11cd6..83811cd61581 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.statusbar.commandline +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -7,8 +8,10 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class ParametersTest : SysuiTestCase() { @Test fun singleArgOptional_returnsNullBeforeParse() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt index 86548d079003..1a7c8a336c81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.commandline +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -24,10 +25,12 @@ import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class ParseableCommandTest : SysuiTestCase() { @Mock private lateinit var pw: PrintWriter diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt index 759f0bcd6ea8..8cf7473345a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt @@ -1,13 +1,16 @@ package com.android.systemui.statusbar.commandline import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class ValueParserTest : SysuiTestCase() { @Test fun parseString() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt index 0fdda62e3b30..ca90f742db40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.connectivity.ui import android.telephony.SubscriptionInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.demomode.DemoModeController @@ -28,12 +29,14 @@ import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class MobileContextProviderTest : SysuiTestCase() { @Mock private lateinit var networkController: NetworkController @Mock private lateinit var dumpManager: DumpManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt index b1c994c2374e..7c98037a5c41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.data.repository +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -29,9 +30,11 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.verify @SmallTest +@RunWith(AndroidJUnit4::class) class KeyguardStatusBarRepositoryImplTest : SysuiTestCase() { private val testScope = TestScope() private val configurationController = mock<ConfigurationController>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt index 4b250b2848a4..7f981eec7ee1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt @@ -23,6 +23,7 @@ import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion @@ -48,9 +49,11 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.verify @SmallTest +@RunWith(AndroidJUnit4::class) class StatusBarModeRepositoryImplTest : SysuiTestCase() { private val testScope = TestScope() private val commandQueue = mock<CommandQueue>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt index f1c7956a1bc7..fffcbb6ccd32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.statusbar.disableflags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class DisableFlagsLoggerTest : SysuiTestCase() { private val disable1Flags = listOf( DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt index 215afb2143df..cf78c71fe047 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt @@ -24,17 +24,20 @@ import android.app.StatusBarManager.DISABLE_EXPAND import android.app.StatusBarManager.DISABLE_NAVIGATION import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS import android.app.StatusBarManager.DISABLE_NOTIFICATION_TICKER +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class DisableStateTrackerTest : SysuiTestCase() { private lateinit var underTest: DisableStateTracker diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt index 31e1fef76383..d2dfc9257e7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt @@ -21,6 +21,7 @@ import android.app.StatusBarManager.DISABLE_CLOCK import android.app.StatusBarManager.DISABLE_NONE import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS import android.content.res.Configuration +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -42,10 +43,12 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class DisableFlagsRepositoryTest : SysuiTestCase() { private lateinit var underTest: DisableFlagsRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index 066ca1c17d30..7a8533ee6d69 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -35,6 +35,7 @@ import android.provider.Settings import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase @@ -69,6 +70,7 @@ import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.time.FakeSystemClock import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock @@ -86,6 +88,7 @@ import java.util.concurrent.Executor @SmallTest @RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidJUnit4::class) class LockscreenSmartspaceControllerTest : SysuiTestCase() { companion object { const val SMARTSPACE_TIME_TOO_EARLY = 1000L @@ -136,6 +139,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { private lateinit var handler: Handler @Mock + private lateinit var bgHandler: Handler + + @Mock private lateinit var datePlugin: BcSmartspaceDataPlugin @Mock @@ -265,6 +271,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { executor, bgExecutor, handler, + bgHandler, Optional.of(datePlugin), Optional.of(weatherPlugin), Optional.of(plugin), @@ -762,6 +769,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { // THEN the existing session is reused and views are registered verify(smartspaceManager, never()).createSmartspaceSession(any()) verify(smartspaceView2).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) + verify(smartspaceView2).setBgHandler(bgHandler) verify(smartspaceView2).setTimeChangedDelegate(any()) verify(smartspaceView2).registerDataProvider(plugin) verify(smartspaceView2).registerConfigProvider(configPlugin) @@ -838,6 +846,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { verify(dateSmartspaceView).setUiSurface( BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) verify(dateSmartspaceView).setTimeChangedDelegate(any()) + verify(dateSmartspaceView).setBgHandler(bgHandler) verify(dateSmartspaceView).registerDataProvider(datePlugin) verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) @@ -851,6 +860,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { verify(weatherSmartspaceView).setUiSurface( BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) verify(weatherSmartspaceView).setTimeChangedDelegate(any()) + verify(weatherSmartspaceView).setBgHandler(bgHandler) verify(weatherSmartspaceView).registerDataProvider(weatherPlugin) verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) @@ -863,6 +873,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) verify(smartspaceView).setTimeChangedDelegate(any()) + verify(smartspaceView).setBgHandler(bgHandler) verify(smartspaceView).registerDataProvider(plugin) verify(smartspaceView).registerConfigProvider(configPlugin) verify(smartspaceSession) @@ -988,6 +999,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { override fun setUiSurface(uiSurface: String) { } + override fun setBgHandler(bgHandler: Handler?) { + } + override fun setTimeChangedDelegate( delegate: BcSmartspaceDataPlugin.TimeChangedDelegate? ) {} @@ -1020,6 +1034,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { override fun setUiSurface(uiSurface: String) { } + override fun setBgHandler(bgHandler: Handler?) { + } + override fun setTimeChangedDelegate( delegate: BcSmartspaceDataPlugin.TimeChangedDelegate? ) {} @@ -1048,6 +1065,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { override fun setUiSurface(uiSurface: String) { } + override fun setBgHandler(bgHandler: Handler?) { + } + override fun setTimeChangedDelegate( delegate: BcSmartspaceDataPlugin.TimeChangedDelegate? ) {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt index b161f84c94ff..05057278f7ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt @@ -23,6 +23,7 @@ import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING @@ -54,11 +55,13 @@ import dagger.Component import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest +@RunWith(AndroidJUnit4::class) class SensitiveContentCoordinatorTest : SysuiTestCase() { val dynamicPrivacyController: DynamicPrivacyController = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt index 8272f5a0d5b9..ef2a2aa67a08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.smartspace.SmartspaceTarget import android.content.ComponentName import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener @@ -43,6 +44,7 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never @@ -51,6 +53,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class SmartspaceDedupingCoordinatorTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index 11996fef6255..99bd4fc549a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection.render +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -37,10 +38,12 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito import org.mockito.Mockito.`when` as whenever @SmallTest +@RunWith(AndroidJUnit4::class) class NodeSpecBuilderTest : SysuiTestCase() { private val mediaContainerController: MediaContainerController = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt index 70d309b10338..ca75ca679c31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection.render +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.GroupEntry @@ -32,6 +33,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.inOrder import org.mockito.Mockito.never @@ -42,6 +44,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class RenderStageManagerTest : SysuiTestCase() { @Mock private lateinit var shadeListBuilder: ShadeListBuilder diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt index b7750795fe71..79ff4be253e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt @@ -15,6 +15,7 @@ package com.android.systemui.statusbar.notification.domain.interactor import android.app.StatusBarManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule @@ -26,8 +27,10 @@ import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class NotificationAlertsInteractorTest : SysuiTestCase() { @Component(modules = [SysUITestModule::class]) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt index a0faab563452..982b7b113a02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.domain.interactor +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -23,8 +24,10 @@ import com.android.systemui.statusbar.notification.data.repository.NotificationL import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class NotificationLaunchAnimationInteractorTest : SysuiTestCase() { private val repository = NotificationLaunchAnimationRepository() private val underTest = NotificationLaunchAnimationInteractor(repository) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt index 3593f5b4963e..133a114fed9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt @@ -13,6 +13,7 @@ */ package com.android.systemui.statusbar.notification.domain.interactor +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule @@ -25,8 +26,10 @@ import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class NotificationsKeyguardInteractorTest : SysuiTestCase() { @SysUISingleton diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt index 334776ca0bcc..277b8876f57f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.domain.interactor import android.service.notification.StatusBarNotification +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -31,8 +32,10 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class RenderNotificationsListInteractorTest : SysuiTestCase() { private val backgroundDispatcher = StandardTestDispatcher() private val testScope = TestScope(backgroundDispatcher) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 7304bd62293d..b8f802600b3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -328,7 +328,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME) - public void setHideSensitive_changeContent_shouldNotDisturbAnimation() throws Exception { + public void setHideSensitive_changeContent_shouldResetAlpha() throws Exception { // Given: A sensitive row that has public version but is not hiding sensitive, // and is during an animation that sets its alpha value to be 0.5f @@ -351,12 +351,12 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { // Then: The alpha value of private layout should be reset to 1, private layout be // INVISIBLE; - // The alpha value of public layout should be 0.5 to preserve the animation state, public - // layout should be VISIBLE + // The alpha value of public layout should be reset to 1 to avoid remaining transparent, + // public layout should be VISIBLE assertEquals(View.INVISIBLE, row.getPrivateLayout().getVisibility()); assertEquals(1f, row.getPrivateLayout().getAlpha(), 0); assertEquals(View.VISIBLE, row.getPublicLayout().getVisibility()); - assertEquals(0.5f, row.getPublicLayout().getAlpha(), 0); + assertEquals(1f, row.getPublicLayout().getAlpha(), 0); } @Test @@ -793,6 +793,73 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test + public void isExpanded_onKeyguard_allowOnKeyguardExpanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setOnKeyguard(true); + row.setUserExpanded(true); + + // THEN + assertThat(row.isExpanded(/*allowOnKeyguard =*/ true)).isTrue(); + } + @Test + public void isExpanded_onKeyguard_notAllowOnKeyguardNotExpanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setOnKeyguard(true); + row.setUserExpanded(true); + + // THEN + assertThat(row.isExpanded(/*allowOnKeyguard =*/ false)).isFalse(); + } + + @Test + public void isExpanded_systemExpanded_expanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setOnKeyguard(false); + row.setSystemExpanded(true); + + // THEN + assertThat(row.isExpanded()).isTrue(); + } + + @Test + public void isExpanded_systemChildExpanded_expanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setOnKeyguard(false); + row.setSystemChildExpanded(true); + + // THEN + assertThat(row.isExpanded()).isTrue(); + } + + @Test + public void isExpanded_userExpanded_expanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setOnKeyguard(false); + row.setSystemExpanded(true); + row.setUserExpanded(true); + + // THEN + assertThat(row.isExpanded()).isTrue(); + } + + @Test + public void isExpanded_userExpandedFalse_notExpanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setOnKeyguard(false); + row.setSystemExpanded(true); + row.setUserExpanded(false); + + // THEN + assertThat(row.isExpanded()).isFalse(); + } + + @Test public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway() throws Exception { final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt index e6cba1c39c85..54a26f722d7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt @@ -23,6 +23,7 @@ import android.os.CancellationSignal import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import android.util.TypedValue +import android.util.TypedValue.COMPLEX_UNIT_SP import android.view.View import android.view.ViewGroup import android.widget.RemoteViews @@ -34,27 +35,39 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor +import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel +import com.android.systemui.statusbar.notification.row.shared.TimerContentModel import com.android.systemui.statusbar.policy.InflatedSmartReplyState import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder import com.android.systemui.statusbar.policy.SmartReplyStateInflater -import com.android.systemui.util.concurrency.mockExecutorHandler +import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor import java.util.concurrent.TimeUnit +import kotlinx.coroutines.DisposableHandle import org.junit.Assert import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq +import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -65,20 +78,24 @@ import org.mockito.kotlin.whenever @RunWithLooper @EnableFlags(NotificationRowContentBinderRefactor.FLAG_NAME) class NotificationRowContentBinderImplTest : SysuiTestCase() { - private lateinit var mNotificationInflater: NotificationRowContentBinderImpl - private lateinit var mBuilder: Notification.Builder - private lateinit var mRow: ExpandableNotificationRow - private lateinit var mHelper: NotificationTestHelper - - private var mCache: NotifRemoteViewCache = mock() - private var mConversationNotificationProcessor: ConversationNotificationProcessor = mock() - private var mInflatedSmartReplyState: InflatedSmartReplyState = mock() - private var mInflatedSmartReplies: InflatedSmartReplyViewHolder = mock() - private var mNotifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider = mock() - private var mHeadsUpStyleProvider: HeadsUpStyleProvider = mock() - private var mNotifLayoutInflaterFactory: NotifLayoutInflaterFactory = mock() - private val mSmartReplyStateInflater: SmartReplyStateInflater = + private lateinit var notificationInflater: NotificationRowContentBinderImpl + private lateinit var builder: Notification.Builder + private lateinit var row: ExpandableNotificationRow + private lateinit var testHelper: NotificationTestHelper + + private val cache: NotifRemoteViewCache = mock() + private val layoutInflaterFactoryProvider = + object : NotifLayoutInflaterFactory.Provider { + override fun provide( + row: ExpandableNotificationRow, + layoutType: Int + ): NotifLayoutInflaterFactory = mock() + } + private val smartReplyStateInflater: SmartReplyStateInflater = object : SmartReplyStateInflater { + private val inflatedSmartReplyState: InflatedSmartReplyState = mock() + private val inflatedSmartReplies: InflatedSmartReplyViewHolder = mock() + override fun inflateSmartReplyViewHolder( sysuiContext: Context, notifPackageContext: Context, @@ -86,37 +103,61 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { existingSmartReplyState: InflatedSmartReplyState?, newSmartReplyState: InflatedSmartReplyState ): InflatedSmartReplyViewHolder { - return mInflatedSmartReplies + return inflatedSmartReplies } override fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState { - return mInflatedSmartReplyState + return inflatedSmartReplyState } } + private var fakeRonContentModel: RichOngoingContentModel? = null + private val fakeRonExtractor = + object : RichOngoingNotificationContentExtractor { + override fun extractContentModel( + entry: NotificationEntry, + builder: Notification.Builder, + systemUIContext: Context, + packageContext: Context + ): RichOngoingContentModel? = fakeRonContentModel + } + + private var fakeRonViewHolder: InflatedContentViewHolder? = null + private val fakeRonViewInflater = + spy( + object : RichOngoingNotificationViewInflater { + override fun inflateView( + contentModel: RichOngoingContentModel, + existingView: View?, + entry: NotificationEntry, + systemUiContext: Context, + parentView: ViewGroup + ): InflatedContentViewHolder? = fakeRonViewHolder + } + ) + @Before fun setUp() { allowTestableLooperAsMainThread() - mBuilder = + builder = Notification.Builder(mContext, "no-id") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") .setContentText("Text") .setStyle(Notification.BigTextStyle().bigText("big text")) - mHelper = NotificationTestHelper(mContext, mDependency) - val row = mHelper.createRow(mBuilder.build()) - mRow = spy(row) - whenever(mNotifLayoutInflaterFactoryProvider.provide(any(), any())) - .thenReturn(mNotifLayoutInflaterFactory) - mNotificationInflater = + testHelper = NotificationTestHelper(mContext, mDependency) + row = spy(testHelper.createRow(builder.build())) + notificationInflater = NotificationRowContentBinderImpl( - mCache, + cache, mock(), - mConversationNotificationProcessor, + mock<ConversationNotificationProcessor>(), + fakeRonExtractor, + fakeRonViewInflater, mock(), - mSmartReplyStateInflater, - mNotifLayoutInflaterFactoryProvider, - mHeadsUpStyleProvider, + smartReplyStateInflater, + layoutInflaterFactoryProvider, + mock<HeadsUpStyleProvider>(), mock() ) } @@ -125,16 +166,16 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { fun testIncreasedHeadsUpBeingUsed() { val params = BindParams() params.usesIncreasedHeadsUpHeight = true - val builder = spy(mBuilder) - mNotificationInflater.inflateNotificationViews( - mRow.entry, - mRow, + val builder = spy(builder) + notificationInflater.inflateNotificationViews( + row.entry, + row, params, true /* inflateSynchronously */, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL, + FLAG_CONTENT_VIEW_ALL, builder, mContext, - mSmartReplyStateInflater + smartReplyStateInflater ) verify(builder).createHeadsUpContentView(true) } @@ -143,80 +184,68 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { fun testIncreasedHeightBeingUsed() { val params = BindParams() params.usesIncreasedHeight = true - val builder = spy(mBuilder) - mNotificationInflater.inflateNotificationViews( - mRow.entry, - mRow, + val builder = spy(builder) + notificationInflater.inflateNotificationViews( + row.entry, + row, params, true /* inflateSynchronously */, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL, + FLAG_CONTENT_VIEW_ALL, builder, mContext, - mSmartReplyStateInflater + smartReplyStateInflater ) verify(builder).createContentView(true) } @Test fun testInflationCallsUpdated() { - inflateAndWait( - mNotificationInflater, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL, - mRow - ) - verify(mRow).onNotificationUpdated() + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) + verify(row).onNotificationUpdated() } @Test fun testInflationOnlyInflatesSetFlags() { - inflateAndWait( - mNotificationInflater, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP, - mRow - ) - Assert.assertNotNull(mRow.privateLayout.headsUpChild) - verify(mRow).onNotificationUpdated() + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row) + Assert.assertNotNull(row.privateLayout.headsUpChild) + verify(row).onNotificationUpdated() } @Test fun testInflationThrowsErrorDoesntCallUpdated() { - mRow.privateLayout.removeAllViews() - mRow.entry.sbn.notification.contentView = + row.privateLayout.removeAllViews() + row.entry.sbn.notification.contentView = RemoteViews(mContext.packageName, R.layout.status_bar) inflateAndWait( true /* expectingException */, - mNotificationInflater, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL, - mRow + notificationInflater, + FLAG_CONTENT_VIEW_ALL, + row ) - Assert.assertTrue(mRow.privateLayout.childCount == 0) - verify(mRow, times(0)).onNotificationUpdated() + Assert.assertTrue(row.privateLayout.childCount == 0) + verify(row, times(0)).onNotificationUpdated() } @Test fun testAsyncTaskRemoved() { - mRow.entry.abortTask() - inflateAndWait( - mNotificationInflater, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL, - mRow - ) - verify(mRow).onNotificationUpdated() + row.entry.abortTask() + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) + verify(row).onNotificationUpdated() } @Test fun testRemovedNotInflated() { - mRow.setRemoved() - mNotificationInflater.setInflateSynchronously(true) - mNotificationInflater.bindContent( - mRow.entry, - mRow, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL, + row.setRemoved() + notificationInflater.setInflateSynchronously(true) + notificationInflater.bindContent( + row.entry, + row, + FLAG_CONTENT_VIEW_ALL, BindParams(), false /* forceInflate */, null /* callback */ ) - Assert.assertNull(mRow.entry.runningTask) + Assert.assertNull(row.entry.runningTask) } @Test @@ -235,11 +264,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { inflateSynchronously = false, isMinimized = false, result = result, - reInflateFlags = NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED, + reInflateFlags = FLAG_CONTENT_VIEW_EXPANDED, inflationId = 0, remoteViewCache = mock(), - entry = mRow.entry, - row = mRow, + entry = row.entry, + row = row, isNewView = true, /* isNewView */ remoteViewClickHandler = { _, _, _ -> true }, callback = @@ -253,7 +282,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { countDownLatch.countDown() } }, - parentLayout = mRow.privateLayout, + parentLayout = row.privateLayout, existingView = null, existingWrapper = null, runningInflations = HashMap(), @@ -275,13 +304,13 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun doesntReapplyDisallowedRemoteView() { - mBuilder.setStyle(Notification.MediaStyle()) - val mediaView = mBuilder.createContentView() - mBuilder.setStyle(Notification.DecoratedCustomViewStyle()) - mBuilder.setCustomContentView( + builder.setStyle(Notification.MediaStyle()) + val mediaView = builder.createContentView() + builder.setStyle(Notification.DecoratedCustomViewStyle()) + builder.setCustomContentView( RemoteViews(context.packageName, com.android.systemui.tests.R.layout.custom_view_dark) ) - val decoratedMediaView = mBuilder.createContentView() + val decoratedMediaView = builder.createContentView() Assert.assertFalse( "The decorated media style doesn't allow a view to be reapplied!", NotificationRowContentBinderImpl.canReapplyRemoteView(mediaView, decoratedMediaView) @@ -292,112 +321,167 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Ignore("b/345418902") fun testUsesSameViewWhenCachedPossibleToReuse() { // GIVEN a cached view. - val contractedRemoteView = mBuilder.createContentView() - whenever( - mCache.hasCachedView( - mRow.entry, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED - ) - ) - .thenReturn(true) - whenever( - mCache.getCachedView( - mRow.entry, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED - ) - ) + val contractedRemoteView = builder.createContentView() + whenever(cache.hasCachedView(row.entry, FLAG_CONTENT_VIEW_CONTRACTED)).thenReturn(true) + whenever(cache.getCachedView(row.entry, FLAG_CONTENT_VIEW_CONTRACTED)) .thenReturn(contractedRemoteView) // GIVEN existing bound view with same layout id. val view = contractedRemoteView.apply(mContext, null /* parent */) - mRow.privateLayout.setContractedChild(view) + row.privateLayout.setContractedChild(view) // WHEN inflater inflates - inflateAndWait( - mNotificationInflater, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED, - mRow - ) + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row) // THEN the view should be re-used Assert.assertEquals( "Binder inflated a new view even though the old one was cached and usable.", view, - mRow.privateLayout.contractedChild + row.privateLayout.contractedChild ) } @Test fun testInflatesNewViewWhenCachedNotPossibleToReuse() { // GIVEN a cached remote view. - val contractedRemoteView = mBuilder.createHeadsUpContentView() - whenever( - mCache.hasCachedView( - mRow.entry, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED - ) - ) - .thenReturn(true) - whenever( - mCache.getCachedView( - mRow.entry, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED - ) - ) + val contractedRemoteView = builder.createHeadsUpContentView() + whenever(cache.hasCachedView(row.entry, FLAG_CONTENT_VIEW_CONTRACTED)).thenReturn(true) + whenever(cache.getCachedView(row.entry, FLAG_CONTENT_VIEW_CONTRACTED)) .thenReturn(contractedRemoteView) // GIVEN existing bound view with different layout id. val view: View = TextView(mContext) - mRow.privateLayout.setContractedChild(view) + row.privateLayout.setContractedChild(view) // WHEN inflater inflates - inflateAndWait( - mNotificationInflater, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED, - mRow - ) + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row) // THEN the view should be a new view Assert.assertNotEquals( "Binder (somehow) used the same view when inflating.", view, - mRow.privateLayout.contractedChild + row.privateLayout.contractedChild ) } @Test fun testInflationCachesCreatedRemoteView() { // WHEN inflater inflates - inflateAndWait( - mNotificationInflater, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED, - mRow - ) + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row) // THEN inflater informs cache of the new remote view - verify(mCache) - .putCachedView( - eq(mRow.entry), - eq(NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED), - any() - ) + verify(cache).putCachedView(eq(row.entry), eq(FLAG_CONTENT_VIEW_CONTRACTED), any()) } @Test fun testUnbindRemovesCachedRemoteView() { // WHEN inflated unbinds content - mNotificationInflater.unbindContent( - mRow.entry, - mRow, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP - ) + notificationInflater.unbindContent(row.entry, row, FLAG_CONTENT_VIEW_HEADS_UP) // THEN inflated informs cache to remove remote view - verify(mCache) - .removeCachedView( - eq(mRow.entry), - eq(NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP) - ) + verify(cache).removeCachedView(eq(row.entry), eq(FLAG_CONTENT_VIEW_HEADS_UP)) + } + + @Test + fun testRonModelRequiredForRonView() { + fakeRonContentModel = null + val ronView = View(context) + fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock()) + // WHEN inflater inflates + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row) + verify(fakeRonViewInflater, never()).inflateView(any(), any(), any(), any(), any()) + } + + @Test + fun testRonModelTriggersInflationOfRonView() { + val mockRonModel = mock<TimerContentModel>() + val ronView = View(context) + val mockBinder = mock<DeferredContentViewBinder>() + + val entry = row.entry + val privateLayout = row.privateLayout + + fakeRonContentModel = mockRonModel + fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder) + // WHEN inflater inflates + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row) + // VERIFY that the inflater is invoked + verify(fakeRonViewInflater) + .inflateView(eq(mockRonModel), any(), eq(entry), any(), eq(privateLayout)) + assertThat(row.privateLayout.contractedChild).isSameInstanceAs(ronView) + verify(mockBinder).setupContentViewBinder() + } + + @Test + fun ronViewAppliesElementsInOrder() { + val oldHandle = mock<DisposableHandle>() + val mockRonModel = mock<TimerContentModel>() + val ronView = View(context) + val mockBinder = mock<DeferredContentViewBinder>() + + row.privateLayout.mContractedBinderHandle = oldHandle + val entry = spy(row.entry) + row.entry = entry + val privateLayout = spy(row.privateLayout) + row.privateLayout = privateLayout + + fakeRonContentModel = mockRonModel + fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder) + // WHEN inflater inflates + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row) + + // Validate that these 4 steps happen in this precise order + inOrder(oldHandle, entry, privateLayout, mockBinder) { + verify(oldHandle).dispose() + verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel }) + verify(privateLayout).setContractedChild(eq(ronView)) + verify(mockBinder).setupContentViewBinder() + } + } + + @Test + fun testRonNotReinflating() { + val handle0 = mock<DisposableHandle>() + val handle1 = mock<DisposableHandle>() + val ronView = View(context) + val mockRonModel1 = mock<TimerContentModel>() + val mockRonModel2 = mock<TimerContentModel>() + val mockBinder1 = mock<DeferredContentViewBinder>() + doReturn(handle1).whenever(mockBinder1).setupContentViewBinder() + + row.privateLayout.mContractedBinderHandle = handle0 + val entry = spy(row.entry) + row.entry = entry + val privateLayout = spy(row.privateLayout) + row.privateLayout = privateLayout + + // WHEN inflater inflates both a model and a view + fakeRonContentModel = mockRonModel1 + fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder1) + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row) + + // Validate that these 4 steps happen in this precise order + inOrder(handle0, entry, privateLayout, mockBinder1, handle1) { + verify(handle0).dispose() + verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel1 }) + verify(privateLayout).setContractedChild(eq(ronView)) + verify(mockBinder1).setupContentViewBinder() + verify(handle1, never()).dispose() + } + + clearInvocations(handle0, entry, privateLayout, mockBinder1, handle1) + + // THEN when the inflater inflates just a model + fakeRonContentModel = mockRonModel2 + fakeRonViewHolder = null + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row) + + // Validate that for reinflation, the only thing we do us update the model + verify(handle1, never()).dispose() + verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel2 }) + verify(privateLayout, never()).setContractedChild(any()) + verify(mockBinder1, never()).setupContentViewBinder() + verify(handle1, never()).dispose() } @Test @@ -453,46 +537,36 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { whenever(view.measuredHeight) .thenReturn( TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_SP, + COMPLEX_UNIT_SP, measuredHeightDp, mContext.resources.displayMetrics ) .toInt() ) - mRow.entry.targetSdk = targetSdk - mRow.entry.sbn.notification.contentView = contentView - return NotificationRowContentBinderImpl.isValidView(view, mRow.entry, mContext.resources) + row.entry.targetSdk = targetSdk + row.entry.sbn.notification.contentView = contentView + return NotificationRowContentBinderImpl.isValidView(view, row.entry, mContext.resources) } @Test fun testInvalidNotificationDoesNotInvokeCallback() { - mRow.privateLayout.removeAllViews() - mRow.entry.sbn.notification.contentView = + row.privateLayout.removeAllViews() + row.entry.sbn.notification.contentView = RemoteViews( mContext.packageName, com.android.systemui.tests.R.layout.invalid_notification_height ) - inflateAndWait( - true, - mNotificationInflater, - NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL, - mRow - ) - Assert.assertEquals(0, mRow.privateLayout.childCount.toLong()) - verify(mRow, times(0)).onNotificationUpdated() + inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, row) + Assert.assertEquals(0, row.privateLayout.childCount.toLong()) + verify(row, times(0)).onNotificationUpdated() } private class ExceptionHolder { - var mException: Exception? = null - - fun setException(exception: Exception?) { - mException = exception - } + var exception: Exception? = null } private class AsyncFailRemoteView(packageName: String?, layoutId: Int) : RemoteViews(packageName, layoutId) { - var mHandler = mockExecutorHandler { p0 -> p0.run() } override fun apply(context: Context, parent: ViewGroup): View { return super.apply(context, parent) @@ -505,7 +579,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { listener: OnViewAppliedListener, handler: InteractionHandler? ): CancellationSignal { - mHandler.post { listener.onError(RuntimeException("Failed to inflate async")) } + executor.execute { listener.onError(RuntimeException("Failed to inflate async")) } return CancellationSignal() } @@ -541,18 +615,17 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { object : InflationCallback { override fun handleInflationException(entry: NotificationEntry, e: Exception) { if (!expectingException) { - exceptionHolder.setException(e) + exceptionHolder.exception = e } countDownLatch.countDown() } override fun onAsyncInflationFinished(entry: NotificationEntry) { if (expectingException) { - exceptionHolder.setException( + exceptionHolder.exception = RuntimeException( "Inflation finished even though there should be an error" ) - ) } countDownLatch.countDown() } @@ -566,7 +639,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { callback /* callback */ ) Assert.assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)) - exceptionHolder.mException?.let { throw it } + exceptionHolder.exception?.let { throw it } } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 21d586b1b5fc..c74a04f1c3e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -199,6 +199,8 @@ public class NotificationTestHelper { mock(NotifRemoteViewCache.class), mock(NotificationRemoteInputManager.class), mock(ConversationNotificationProcessor.class), + mock(RichOngoingNotificationContentExtractor.class), + mock(RichOngoingNotificationViewInflater.class), mock(Executor.class), new MockSmartReplyInflater(), mock(NotifLayoutInflaterFactory.Provider.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt index 1dfcb38b247b..578950fa88b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.notification.stack +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE @@ -31,12 +32,14 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class DisplaySwitchNotificationsHiderTrackerTest : SysuiTestCase() { private val testScope = TestScope() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 48e8f88a15c0..c1f2cb77f411 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -10,6 +10,8 @@ import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FeatureFlags import com.android.systemui.res.R @@ -30,8 +32,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations /** Tests for {@link NotificationShelf}. */ @SmallTest @@ -332,6 +334,101 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + fun updateState_lastViewAlmostBelowShelf_completelyInShelf() { + val viewStart = 0f + val shelfClipStart = 0.001f + + val expandableView = mock(ExpandableView::class.java) + whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(expandableView.translationY).thenReturn(viewStart) + whenever(expandableView.actualHeight).thenReturn(20) + + whenever(expandableView.minHeight).thenReturn(20) + whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(expandableView.isInShelf).thenReturn(true) + + whenever(ambientState.isOnKeyguard).thenReturn(true) + whenever(ambientState.isExpansionChanging).thenReturn(false) + whenever(ambientState.isShadeExpanded).thenReturn(true) + + val amountInShelf = + shelf.getAmountInShelf( + /* i= */ 0, + /* view= */ expandableView, + /* scrollingFast= */ false, + /* expandingAnimated= */ false, + /* isLastChild= */ true, + shelfClipStart + ) + assertEquals(1f, amountInShelf) + } + + @Test + @EnableSceneContainer + fun updateState_withViewInShelf_showShelf() { + // GIVEN a view is scrolled into the shelf + val stackTop = 200f + val stackHeight = 800f + whenever(ambientState.stackTop).thenReturn(stackTop) + whenever(ambientState.stackHeight).thenReturn(stackHeight) + val shelfTop = stackTop + stackHeight - shelf.height + val stackScrollAlgorithmState = StackScrollAlgorithmState() + val viewInShelf = mock(ExpandableView::class.java) + + whenever(ambientState.isShadeExpanded).thenReturn(true) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(viewInShelf) + whenever(viewInShelf.viewState).thenReturn(ExpandableViewState()) + whenever(viewInShelf.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(viewInShelf.translationY).thenReturn(shelfTop) + whenever(viewInShelf.actualHeight).thenReturn(10) + whenever(viewInShelf.isInShelf).thenReturn(true) + whenever(viewInShelf.minHeight).thenReturn(10) + whenever(viewInShelf.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(viewInShelf.isInShelf).thenReturn(true) + + stackScrollAlgorithmState.visibleChildren.add(viewInShelf) + stackScrollAlgorithmState.firstViewInShelf = viewInShelf + + // WHEN Shelf's ViewState is updated + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN the shelf is visible, and positioned correctly + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(false, shelfState.hidden) + assertEquals(shelf.height, shelfState.height) + assertEquals(shelfTop, shelfState.yTranslation) + } + + @Test + @EnableSceneContainer + fun updateState_withNullLastVisibleBackgroundChild_hideShelf_withSceneContainer() { + // GIVEN + val stackTop = 200f + val stackHeight = 800f + whenever(ambientState.stackTop).thenReturn(stackTop) + whenever(ambientState.stackHeight).thenReturn(stackHeight) + val paddingBetweenElements = + context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) + whenever(ambientState.isShadeExpanded).thenReturn(true) + val lastVisibleBackgroundChild = mock<ExpandableView>() + val expandableViewState = ExpandableViewState() + whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) + val stackScrollAlgorithmState = StackScrollAlgorithmState() + stackScrollAlgorithmState.firstViewInShelf = mock() + + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(null) + + // WHEN + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(true, shelfState.hidden) + assertEquals(stackTop + stackHeight + paddingBetweenElements, shelfState.yTranslation) + } + + @Test + @DisableSceneContainer fun updateState_withNullLastVisibleBackgroundChild_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) @@ -358,6 +455,35 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun updateState_withNullFirstViewInShelf_hideShelf_withSceneContainer() { + // GIVEN + val stackTop = 200f + val stackHeight = 800f + whenever(ambientState.stackTop).thenReturn(stackTop) + whenever(ambientState.stackHeight).thenReturn(stackHeight) + val paddingBetweenElements = + context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) + whenever(ambientState.isShadeExpanded).thenReturn(true) + val lastVisibleBackgroundChild = mock<ExpandableView>() + val expandableViewState = ExpandableViewState() + whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) + val stackScrollAlgorithmState = StackScrollAlgorithmState() + + stackScrollAlgorithmState.firstViewInShelf = null + + // WHEN + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(true, shelfState.hidden) + assertEquals(stackTop + stackHeight + paddingBetweenElements, shelfState.yTranslation) + } + + @Test + @DisableSceneContainer fun updateState_withNullFirstViewInShelf_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) @@ -384,6 +510,35 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun updateState_withCollapsedShade_hideShelf_withSceneContainer() { + // GIVEN + val stackTop = 200f + val stackHeight = 800f + whenever(ambientState.stackTop).thenReturn(stackTop) + whenever(ambientState.stackHeight).thenReturn(stackHeight) + val paddingBetweenElements = + context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) + val lastVisibleBackgroundChild = mock<ExpandableView>() + val expandableViewState = ExpandableViewState() + whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) + val stackScrollAlgorithmState = StackScrollAlgorithmState() + stackScrollAlgorithmState.firstViewInShelf = mock() + + whenever(ambientState.isShadeExpanded).thenReturn(false) + + // WHEN + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(true, shelfState.hidden) + assertEquals(stackTop + stackHeight + paddingBetweenElements, shelfState.yTranslation) + } + + @Test + @DisableSceneContainer fun updateState_withCollapsedShade_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) @@ -410,6 +565,49 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun updateState_withHiddenSectionBeforeShelf_hideShelf_withSceneContainer() { + // GIVEN + val stackTop = 200f + val stackHeight = 800f + whenever(ambientState.stackTop).thenReturn(stackTop) + whenever(ambientState.stackHeight).thenReturn(stackHeight) + val paddingBetweenElements = + context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) + whenever(ambientState.isShadeExpanded).thenReturn(true) + val lastVisibleBackgroundChild = mock<ExpandableView>() + val expandableViewState = ExpandableViewState() + whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) + val stackScrollAlgorithmState = StackScrollAlgorithmState() + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) + + val ssaVisibleChild = mock<ExpandableView>() + val ssaVisibleChildState = ExpandableViewState() + ssaVisibleChildState.hidden = true + whenever(ssaVisibleChild.viewState).thenReturn(ssaVisibleChildState) + + val ssaVisibleChild1 = mock<ExpandableView>() + val ssaVisibleChildState1 = ExpandableViewState() + ssaVisibleChildState1.hidden = true + whenever(ssaVisibleChild1.viewState).thenReturn(ssaVisibleChildState1) + + stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild) + stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild1) + whenever(ambientState.isExpansionChanging).thenReturn(true) + whenever(ambientState.expansionFraction).thenReturn(1f) + stackScrollAlgorithmState.firstViewInShelf = ssaVisibleChild1 + + // WHEN + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(true, shelfState.hidden) + assertEquals(stackTop + stackHeight + paddingBetweenElements, shelfState.yTranslation) + } + + @Test + @DisableSceneContainer fun updateState_withHiddenSectionBeforeShelf_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) @@ -461,12 +659,9 @@ open class NotificationShelfTest : SysuiTestCase() { expectedAlpha: Float ) { val sbnMock: StatusBarNotification = mock() - val mockEntry = mock<NotificationEntry>().apply { - whenever(this.sbn).thenReturn(sbnMock) - } + val mockEntry = mock<NotificationEntry>().apply { whenever(this.sbn).thenReturn(sbnMock) } val row = ExpandableNotificationRow(mContext, null, mockEntry) - whenever(ambientState.lastVisibleBackgroundChild) - .thenReturn(row) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(row) whenever(ambientState.isExpansionChanging).thenReturn(true) whenever(ambientState.expansionFraction).thenReturn(expansionFraction) whenever(hostLayoutController.speedBumpIndex).thenReturn(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 12f3ef3cf553..770c4243afc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -214,6 +214,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + @DisableSceneContainer // TODO(b/332574413) cover stack bounds integration with tests public void testUpdateStackHeight_qsExpansionGreaterThanZero() { final float expansionFraction = 0.2f; final float overExpansion = 50f; @@ -261,15 +262,62 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - public void updateStackEndHeightAndStackHeight_normallyUpdatesBoth() { - final float expansionFraction = 0.5f; + @EnableSceneContainer + public void updateStackEndHeightAndStackHeight_shadeFullyExpanded_withSceneContainer() { + final float stackTop = 200f; + final float stackCutoff = 1000f; + final float stackEndHeight = stackCutoff - stackTop; + mAmbientState.setStackTop(stackTop); + mAmbientState.setStackCutoff(stackCutoff); mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); + clearInvocations(mAmbientState); + + // WHEN shade is fully expanded + mStackScroller.updateStackEndHeightAndStackHeight(/* fraction = */ 1.0f); + + // THEN stackHeight and stackEndHeight are the same + verify(mAmbientState).setStackEndHeight(stackEndHeight); + verify(mAmbientState).setStackHeight(stackEndHeight); + } - // Validate that by default we update everything + @Test + @EnableSceneContainer + public void updateStackEndHeightAndStackHeight_shadeExpanding_withSceneContainer() { + final float stackTop = 200f; + final float stackCutoff = 1000f; + final float stackEndHeight = stackCutoff - stackTop; + mAmbientState.setStackTop(stackTop); + mAmbientState.setStackCutoff(stackCutoff); + mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); clearInvocations(mAmbientState); + + // WHEN shade is expanding + final float expansionFraction = 0.5f; mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction); - verify(mAmbientState).setStackEndHeight(anyFloat()); - verify(mAmbientState).setStackHeight(anyFloat()); + + // THEN stackHeight is changed by the expansion frac + verify(mAmbientState).setStackEndHeight(stackEndHeight); + verify(mAmbientState).setStackHeight(stackEndHeight * 0.75f); + } + + @Test + @EnableSceneContainer + public void updateStackEndHeightAndStackHeight_shadeOverscrolledToTop_withSceneContainer() { + // GIVEN stack scrolled over the top, stack top is negative + final float stackTop = -2000f; + final float stackCutoff = 1000f; + final float stackEndHeight = stackCutoff - stackTop; + mAmbientState.setStackTop(stackTop); + mAmbientState.setStackCutoff(stackCutoff); + mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); + clearInvocations(mAmbientState); + + // WHEN stack is updated + mStackScroller.updateStackEndHeightAndStackHeight(/* fraction = */ 1.0f); + + // THEN stackHeight is measured from the stack top + verify(mAmbientState).setStackEndHeight(stackEndHeight); + verify(mAmbientState).setStackHeight(stackEndHeight); } @Test @@ -891,6 +939,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + @DisableSceneContainer // NSSL has no more scroll logic when SceneContainer is on public void testNormalShade_hasNoTopOverscroll() { mTestableResources .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index a6fb7187ff5f..b12c0986bdfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -5,11 +5,15 @@ import android.content.pm.PackageManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.res.R @@ -37,6 +41,7 @@ import org.junit.Assume import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.any import org.mockito.Mockito.eq import org.mockito.Mockito.mock @@ -44,6 +49,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest +@RunWith(AndroidJUnit4::class) class StackScrollAlgorithmTest : SysuiTestCase() { @JvmField @Rule var expect: Expect = Expect.create() @@ -63,7 +69,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { EmptyShadeView(context, /* attrs= */ null).apply { layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) } - private val footerView = FooterView(context, /*attrs=*/ null) + private val footerView = FooterView(context, /* attrs= */ null) @OptIn(ExperimentalCoroutinesApi::class) private val ambientState = AmbientState( @@ -123,6 +129,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() { whenever(notificationRow.isPinned).thenReturn(true) whenever(notificationRow.isHeadsUp).thenReturn(true) @@ -165,6 +172,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() { // Given: the shade is open and scrolled to the bottom to show the QuickSettings @@ -181,6 +189,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() { // Given: the shade is open and scrolled to the bottom to show the QuickSettings @@ -269,6 +278,27 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun resetViewStates_emptyShadeView_isCenteredVertically_withSceneContainer() { + stackScrollAlgorithm.initView(context) + hostView.removeAllViews() + hostView.addView(emptyShadeView) + ambientState.layoutMaxHeight = maxPanelHeight.toInt() + + val stackTop = 200f + val stackBottom = 2000f + val stackHeight = stackBottom - stackTop + ambientState.stackTop = stackTop + ambientState.stackCutoff = stackBottom + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + val centeredY = stackTop + stackHeight / 2f - emptyShadeView.height / 2f + assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY) + } + + @Test + @DisableSceneContainer fun resetViewStates_emptyShadeView_isCenteredVertically() { stackScrollAlgorithm.initView(context) hostView.removeAllViews() @@ -520,6 +550,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { assertThat((footerView.viewState as FooterViewState).hideContent).isTrue() } + @DisableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) @Test fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() { ambientState.isClearAllInProgress = true @@ -1154,6 +1185,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { assertFalse(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)) } + // endregion private fun createHunViewMock( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index f0bc655a554d..665544d094e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -61,7 +61,7 @@ import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastController.CastDevice; +import com.android.systemui.statusbar.policy.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceControlsController; import com.android.systemui.statusbar.policy.HotspotController; @@ -422,9 +422,17 @@ public class AutoTileManagerTest extends SysuiTestCase { } private static List<CastDevice> buildFakeCastDevice(boolean isCasting) { - CastDevice cd = new CastDevice(); - cd.state = isCasting ? CastDevice.STATE_CONNECTED : CastDevice.STATE_DISCONNECTED; - return Collections.singletonList(cd); + CastDevice.CastState state = isCasting + ? CastDevice.CastState.Connected + : CastDevice.CastState.Disconnected; + return Collections.singletonList( + new CastDevice( + "id", + /* name= */ null, + /* description= */ null, + /* state= */ state, + /* origin= */ CastDevice.CastOrigin.MediaProjection, + /* tag= */ null)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt index c44c178db38a..2f810276d7b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone import android.content.pm.UserInfo import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.settings.UserTracker @@ -29,12 +30,14 @@ import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class ManagedProfileControllerImplTest : SysuiTestCase() { private val mainExecutor: FakeExecutor = FakeExecutor(FakeSystemClock()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index f2f336c45d7f..dfee2ed43dc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -21,11 +21,14 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResourcesManager import android.content.SharedPreferences import android.os.UserManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.telecom.TelecomManager import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor @@ -39,6 +42,7 @@ import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.policy.BluetoothController import com.android.systemui.statusbar.policy.CastController +import com.android.systemui.statusbar.policy.CastDevice import com.android.systemui.statusbar.policy.DataSaverController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HotspotController @@ -54,6 +58,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.DateFormatUtil import com.android.systemui.util.time.FakeSystemClock import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -78,6 +83,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.kotlin.argumentCaptor @RunWith(AndroidJUnit4::class) @RunWithLooper @@ -87,6 +93,8 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { companion object { private const val ALARM_SLOT = "alarm" + private const val CAST_SLOT = "cast" + private const val SCREEN_RECORD_SLOT = "screen_record" private const val CONNECTED_DISPLAY_SLOT = "connected_display" private const val MANAGED_PROFILE_SLOT = "managed_profile" } @@ -271,6 +279,101 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) } + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + fun cast_chipsFlagOff_iconShown() { + statusBarPolicy.init() + clearInvocations(iconController) + + val callbackCaptor = argumentCaptor<CastController.Callback>() + verify(castController).addCallback(callbackCaptor.capture()) + + whenever(castController.castDevices) + .thenReturn( + listOf( + CastDevice( + "id", + "name", + "description", + CastDevice.CastState.Connected, + CastDevice.CastOrigin.MediaProjection, + ) + ) + ) + callbackCaptor.firstValue.onCastDevicesChanged() + + verify(iconController).setIconVisibility(CAST_SLOT, true) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + fun cast_chipsFlagOn_noCallbackRegistered() { + statusBarPolicy.init() + + verify(castController, never()).addCallback(any()) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + fun screenRecord_chipsFlagOff_iconShown_forAllStates() { + statusBarPolicy.init() + clearInvocations(iconController) + + val callbackCaptor = argumentCaptor<RecordingController.RecordingStateChangeCallback>() + verify(recordingController).addCallback(callbackCaptor.capture()) + + callbackCaptor.firstValue.onCountdown(3000) + testableLooper.processAllMessages() + verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, true) + clearInvocations(iconController) + + callbackCaptor.firstValue.onCountdownEnd() + testableLooper.processAllMessages() + verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, false) + clearInvocations(iconController) + + callbackCaptor.firstValue.onRecordingStart() + testableLooper.processAllMessages() + verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, true) + clearInvocations(iconController) + + callbackCaptor.firstValue.onRecordingEnd() + testableLooper.processAllMessages() + verify(iconController).setIconVisibility(SCREEN_RECORD_SLOT, false) + clearInvocations(iconController) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + fun screenRecord_chipsFlagOn_noCallbackRegistered() { + statusBarPolicy.init() + + verify(recordingController, never()).addCallback(any()) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + fun screenRecord_chipsFlagOn_methodsDoNothing() { + statusBarPolicy.init() + clearInvocations(iconController) + + statusBarPolicy.onCountdown(3000) + testableLooper.processAllMessages() + verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any()) + + statusBarPolicy.onCountdownEnd() + testableLooper.processAllMessages() + verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any()) + + statusBarPolicy.onRecordingStart() + testableLooper.processAllMessages() + verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any()) + + statusBarPolicy.onRecordingEnd() + testableLooper.processAllMessages() + verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any()) + } + private fun createAlarmInfo(): AlarmManager.AlarmClockInfo { return AlarmManager.AlarmClockInfo(10L, null) } @@ -315,13 +418,18 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor { private val flow = MutableSharedFlow<State>() + suspend fun emit(value: State) = flow.emit(value) + override val connectedDisplayState: Flow<State> get() = flow + override val connectedDisplayAddition: Flow<Unit> get() = TODO("Not yet implemented") + override val pendingDisplay: Flow<PendingDisplay?> get() = TODO("Not yet implemented") + override val concurrentDisplaysInProgress: Flow<Boolean> get() = TODO("Not yet implemented") } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index ba38f871cf9d..25314f379e7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -27,6 +27,7 @@ import android.view.View import android.view.ViewTreeObserver import android.view.ViewTreeObserver.OnPreDrawListener import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.SysuiTestCase @@ -54,6 +55,7 @@ import java.util.Optional import javax.inject.Provider 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.mock @@ -64,6 +66,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var shadeViewController: ShadeViewController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt index 5a0e13d02b92..2d9880a48f80 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone.domain.interactor +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -25,8 +26,10 @@ import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepositor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class LightsOutInteractorTest : SysuiTestCase() { private val statusBarModeRepository = FakeStatusBarModeRepository() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt index e0f1e1a46d3b..219b16f1e0ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone.fragment +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -26,9 +27,11 @@ import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.mock @SmallTest +@RunWith(AndroidJUnit4::class) class CollapsedStatusBarFragmentLoggerTest : SysuiTestCase() { private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt index 022b5d295256..9f6f51a28764 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt @@ -19,14 +19,17 @@ import android.app.StatusBarManager.DISABLE_CLOCK import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP import android.app.StatusBarManager.DISABLE_SYSTEM_INFO +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.phone.fragment.StatusBarVisibilityModel.Companion.createDefaultModel import com.android.systemui.statusbar.phone.fragment.StatusBarVisibilityModel.Companion.createModelFromFlags import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class StatusBarVisibilityModelTest : SysuiTestCase() { @Test fun createDefaultModel_everythingEnabled() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 7273d9f55259..5174ec7189ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -25,11 +25,11 @@ import android.app.Person import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.notification.NotificationListenerService.REASON_USER_STOPPED -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS @@ -80,7 +80,7 @@ private const val PROC_STATE_VISIBLE = ActivityManager.PROCESS_STATE_TOP private const val PROC_STATE_INVISIBLE = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @OptIn(ExperimentalCoroutinesApi::class) class OngoingCallControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt index ecec124e83e2..5ce936d11a64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.statusbar.phone.ongoingcall +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class OngoingCallLoggerTest : SysuiTestCase() { private val uiEventLoggerFake = UiEventLoggerFake() private val ongoingCallLogger = OngoingCallLogger(uiEventLoggerFake) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt index d6624ca6c559..27c2366ebdb5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.statusbar.phone.ongoingcall.data.repository +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class OngoingCallRepositoryTest : SysuiTestCase() { private val underTest = OngoingCallRepository() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt index 7e523fbd8747..19abbd58ad3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone.ui import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.statusbar.StatusBarIcon import com.android.systemui.SysuiTestCase @@ -32,11 +33,13 @@ import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class StatusBarIconControllerImplTest : SysuiTestCase() { private lateinit var underTest: StatusBarIconControllerImpl diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java index 14bce9c54a20..891ff38764fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java @@ -25,11 +25,11 @@ 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.RunWithLooper; import android.view.ViewGroup; import android.widget.LinearLayout; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.StatusBarIcon; @@ -55,7 +55,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @SmallTest public class StatusBarIconControllerTest extends LeakCheckedTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt index 1c9f52366fe0..b823333978f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer @@ -33,12 +34,14 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest @OptIn(ExperimentalCoroutinesApi::class) @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@RunWith(AndroidJUnit4::class) class AirplaneModeViewModelImplTest : SysuiTestCase() { private lateinit var underTest: AirplaneModeViewModelImpl diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt index 6028712c29fc..f9ae5ff9bbc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.ethernet.domain +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.AccessibilityContentDescriptions import com.android.systemui.res.R @@ -28,8 +29,10 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class EthernetInteractorTest : SysuiTestCase() { private val connectivityRepository = FakeConnectivityRepository() private val underTest = EthernetInteractor(connectivityRepository) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt index 3de50c97a258..4a2f6f281566 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt @@ -21,15 +21,18 @@ import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class SystemUiCarrierConfigTest : SysuiTestCase() { lateinit var underTest: SystemUiCarrierConfig diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt index 932c4a17cae2..320c148f497c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt @@ -20,6 +20,7 @@ import android.content.Intent import android.os.PersistableBundle import android.telephony.CarrierConfigManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.systemui.SysuiTestCase @@ -37,6 +38,7 @@ import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -45,6 +47,7 @@ import org.mockito.quality.Strictness @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class CarrierConfigRepositoryTest : SysuiTestCase() { private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt index 6785de930849..db6f59276eb6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -43,11 +43,12 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.After +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.runner.parameterized.Parameter import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameters /** * Parameterized test for all of the common values of [FakeNetworkEventModel]. This test simply @@ -56,7 +57,7 @@ import org.junit.runners.Parameterized.Parameters */ @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index a41bc0d87615..5e0d2fb05e59 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons.THREE_G @@ -49,9 +50,11 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { private val dumpManager: DumpManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index 3c13906dbd43..fd4c3702a666 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -23,6 +23,7 @@ import android.telephony.SignalStrength import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyCallback import android.telephony.TelephonyManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -58,6 +59,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.never import org.mockito.Mockito.verify @@ -69,6 +71,7 @@ import org.mockito.Mockito.verify @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class FullMobileConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: FullMobileConnectionRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 6d8bf55d489b..8fd0b31b82f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -66,6 +66,7 @@ import android.telephony.TelephonyManager.EXTRA_SPN import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase @@ -107,6 +108,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -114,6 +116,7 @@ import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class MobileConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionRepositoryImpl private lateinit var connectionsRepo: FakeMobileConnectionsRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 14884182d656..30e96f10918d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -20,6 +20,7 @@ import android.platform.test.annotations.EnableFlags import android.telephony.CellSignalStrength import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileIconCarrierIdOverrides import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl @@ -52,11 +53,13 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class MobileIconInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconInteractor private val mobileMappingsProxy = FakeMobileMappingsProxy() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 58d9ee3935fd..cc0eae7e6fea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -21,6 +21,7 @@ import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase @@ -50,11 +51,13 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class MobileIconsInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconsInteractor private lateinit var connectivityRepository: FakeConnectivityRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt index 755aaa6541ff..4511be954692 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -32,10 +33,12 @@ import java.io.PrintWriter import java.io.StringWriter import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class MobileViewLoggerTest : SysuiTestCase() { private val buffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10) private val stringWriter = StringWriter() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt index cdc4733715e5..fc1ea227f1bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.satellite.data import android.telephony.TelephonyManager import android.telephony.satellite.SatelliteManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -41,9 +42,11 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.verify @SmallTest +@RunWith(AndroidJUnit4::class) class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() { private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt index f77fd1999007..87693891a281 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.satellite.data.demo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -29,8 +30,10 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class DemoDeviceBasedSatelliteRepositoryTest : SysuiTestCase() { private val testDispatcher = StandardTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt index 890a2e40c94c..73ac6e3573d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt @@ -32,10 +32,12 @@ import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_NOT_CO import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN +import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ERROR import android.telephony.satellite.SatelliteManager.SatelliteException import android.telephony.satellite.SatelliteModemStateCallback import android.telephony.satellite.SatelliteProvisionStateCallback import android.telephony.satellite.SatelliteSupportedStateCallback +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -60,8 +62,10 @@ import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.doAnswer import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -71,6 +75,7 @@ import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: DeviceBasedSatelliteRepositoryImpl @@ -354,13 +359,142 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { } @Test - fun satelliteProvisioned_supported_tracksCallback() = + fun satelliteProvisioned_returnsException_defaultsToFalse() = + testScope.runTest { + // GIVEN satellite is supported on device + doAnswer { + val callback: OutcomeReceiver<Boolean, SatelliteException> = + it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException> + callback.onResult(true) + } + .whenever(satelliteManager) + .requestIsSupported(any(), any()) + + // GIVEN satellite returns an error when asked if provisioned + doAnswer { + val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException> + receiver.onError(SatelliteException(SATELLITE_RESULT_ERROR)) + null + } + .whenever(satelliteManager) + .requestIsProvisioned( + any(), + any<OutcomeReceiver<Boolean, SatelliteException>>() + ) + + // GIVEN we've been up long enough to start querying + systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME) + + underTest = + DeviceBasedSatelliteRepositoryImpl( + Optional.of(satelliteManager), + telephonyManager, + dispatcher, + testScope.backgroundScope, + logBuffer = FakeLogBuffer.Factory.create(), + verboseLogBuffer = FakeLogBuffer.Factory.create(), + systemClock, + ) + + // WHEN we try to check for provisioned status + val provisioned by collectLastValue(underTest.isSatelliteProvisioned) + + // THEN well, first we don't throw... + // AND THEN we assume that it's not provisioned + assertThat(provisioned).isFalse() + } + + @Test + fun satelliteProvisioned_throwsWhenQuerying_defaultsToFalse() = + testScope.runTest { + // GIVEN satellite is supported on device + doAnswer { + val callback: OutcomeReceiver<Boolean, SatelliteException> = + it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException> + callback.onResult(true) + } + .whenever(satelliteManager) + .requestIsSupported(any(), any()) + + // GIVEN satellite throws when asked if provisioned + whenever(satelliteManager.requestIsProvisioned(any(), any())) + .thenThrow(SecurityException()) + + // GIVEN we've been up long enough to start querying + systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME) + + underTest = + DeviceBasedSatelliteRepositoryImpl( + Optional.of(satelliteManager), + telephonyManager, + dispatcher, + testScope.backgroundScope, + logBuffer = FakeLogBuffer.Factory.create(), + verboseLogBuffer = FakeLogBuffer.Factory.create(), + systemClock, + ) + + // WHEN we try to check for provisioned status + val provisioned by collectLastValue(underTest.isSatelliteProvisioned) + + // THEN well, first we don't throw... + // AND THEN we assume that it's not provisioned + assertThat(provisioned).isFalse() + } + + @Test + fun satelliteProvisioned_supported_provisioned_queriesInitialStateBeforeCallbacks() = + testScope.runTest { + // GIVEN satellite is supported, and provisioned + setUpRepo( + uptime = MIN_UPTIME, + satMan = satelliteManager, + satelliteSupported = true, + initialSatelliteIsProvisioned = true, + ) + + val provisioned by collectLastValue(underTest.isSatelliteProvisioned) + + runCurrent() + + // THEN the current state is requested + verify(satelliteManager, atLeastOnce()).requestIsProvisioned(any(), any()) + + // AND the state is correct + assertThat(provisioned).isTrue() + } + + @Test + fun satelliteProvisioned_supported_notProvisioned_queriesInitialStateBeforeCallbacks() = + testScope.runTest { + // GIVEN satellite is supported, and provisioned + setUpRepo( + uptime = MIN_UPTIME, + satMan = satelliteManager, + satelliteSupported = true, + initialSatelliteIsProvisioned = false, + ) + + val provisioned by collectLastValue(underTest.isSatelliteProvisioned) + + runCurrent() + + // THEN the current state is requested + verify(satelliteManager, atLeastOnce()).requestIsProvisioned(any(), any()) + + // AND the state is correct + assertThat(provisioned).isFalse() + } + + @Test + fun satelliteProvisioned_supported_notInitiallyProvisioned_tracksCallback() = testScope.runTest { // GIVEN satellite is not supported setUpRepo( uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true, + initialSatelliteIsProvisioned = false, ) val provisioned by collectLastValue(underTest.isSatelliteProvisioned) @@ -416,6 +550,8 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { // THEN listeners are re-registered verify(satelliteManager, times(2)).registerForProvisionStateChanged(any(), any()) + // AND the state is queried again + verify(satelliteManager, times(2)).requestIsProvisioned(any(), any()) } @Test @@ -632,6 +768,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { uptime: Long = MIN_UPTIME, satMan: SatelliteManager? = satelliteManager, satelliteSupported: Boolean = true, + initialSatelliteIsProvisioned: Boolean = true, ) { doAnswer { val callback: OutcomeReceiver<Boolean, SatelliteException> = @@ -641,6 +778,14 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { .whenever(satelliteManager) .requestIsSupported(any(), any()) + doAnswer { + val callback: OutcomeReceiver<Boolean, SatelliteException> = + it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException> + callback.onResult(initialSatelliteIsProvisioned) + } + .whenever(satelliteManager) + .requestIsProvisioned(any(), any()) + systemClock.setUptimeMillis(Process.getStartUptimeMillis() + uptime) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt index 2e5ebb3e3194..cd0390ecf9c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.satellite.domain.interactor import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG import com.android.systemui.SysuiTestCase @@ -38,8 +39,10 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { private lateinit var underTest: DeviceBasedSatelliteInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt index c39e301155fe..64b07fcaa8c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon @@ -41,9 +42,11 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.runner.RunWith import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { private lateinit var underTest: DeviceBasedSatelliteViewModel private lateinit var interactor: DeviceBasedSatelliteInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt index c43778a4ae51..4c2bb7f8069d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.statusbar.pipeline.shared.data.model +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogMessageImpl import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class DefaultConnectionModelTest : SysuiTestCase() { @Test fun messageInitializerAndPrinter_isValidatedFalse_hasCorrectInfo() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt index fa4e91b68a5e..f486787e9cc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt @@ -25,6 +25,7 @@ import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -50,6 +51,7 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -57,6 +59,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class ConnectivityRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: ConnectivityRepositoryImpl diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt index 028a58c6e1b0..c3ad0f9d5148 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.pipeline.shared.ui.view import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT @@ -30,7 +30,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class ModernStatusBarViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt index ca9df57e8798..a6cdfe23aa5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.pipeline.shared.ui.view import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.StatusBarIconView @@ -32,7 +32,7 @@ import org.junit.runner.RunWith * method. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class SingleBindableStatusBarIconViewTest : SysuiTestCase() { private lateinit var binding: SingleBindableStatusBarIconViewBinding diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 92de8662928f..b4c6106150b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -60,9 +61,11 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { private val kosmos = Kosmos().also { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt index 0cb3329dcb4d..2238bff8eb93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION import com.android.systemui.SysuiTestCase @@ -55,8 +56,10 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class InternetTileViewModelTest : SysuiTestCase() { private lateinit var underTest: InternetTileViewModel private lateinit var mobileIconsInteractor: MobileIconsInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt index ba035bec340c..eb6b068ca8a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.wifi.shared.model import android.net.wifi.WifiManager.UNKNOWN_SSID import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableRowLogger @@ -25,8 +26,10 @@ import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkMode import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL import com.google.common.truth.Truth.assertThat import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class WifiNetworkModelTest : SysuiTestCase() { @Test fun active_levelsInValidRange_noException() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 9ab64d650e60..93071bd9305a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -43,6 +43,7 @@ import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.bluetooth.BluetoothEventManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java index 68c1b8d3f44f..59b20c824739 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java @@ -9,12 +9,13 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.pm.PackageManager; import android.media.MediaRouter; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -33,7 +34,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class CastControllerImplTest extends SysuiTestCase { @@ -52,8 +53,12 @@ public class CastControllerImplTest extends SysuiTestCase { mContext.addMockSystemService(MediaRouter.class, mMediaRouter); mContext.addMockSystemService(MediaProjectionManager.class, mMediaProjectionManager); when(mMediaProjectionManager.getActiveProjectionInfo()).thenReturn(mProjection); + when(mProjection.getPackageName()).thenReturn("fake.package"); - mController = new CastControllerImpl(mContext, mock(DumpManager.class)); + mController = new CastControllerImpl( + mContext, + mock(PackageManager.class), + mock(DumpManager.class)); } @Test @@ -148,16 +153,26 @@ public class CastControllerImplTest extends SysuiTestCase { @Test public void hasConnectedCastDevice_connected() { - CastController.CastDevice castDevice = new CastController.CastDevice(); - castDevice.state = CastController.CastDevice.STATE_CONNECTED; + CastDevice castDevice = new CastDevice( + "id", + /* name= */ null, + /* description= */ null, + /* state= */ CastDevice.CastState.Connected, + /* origin= */ CastDevice.CastOrigin.MediaProjection, + /* tag= */ null); mController.startCasting(castDevice); assertTrue(mController.hasConnectedCastDevice()); } @Test public void hasConnectedCastDevice_notConnected() { - CastController.CastDevice castDevice = new CastController.CastDevice(); - castDevice.state = CastController.CastDevice.STATE_CONNECTING; + CastDevice castDevice = new CastDevice( + "id", + /* name= */ null, + /* description= */ null, + /* state= */ CastDevice.CastState.Connecting, + /* origin= */ CastDevice.CastOrigin.MediaProjection, + /* tag= */ null); mController.startCasting(castDevice); assertTrue(mController.hasConnectedCastDevice()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt new file mode 100644 index 000000000000..03ad66cd4cce --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import android.Manifest +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.media.MediaRouter +import android.media.projection.MediaProjectionInfo +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.CastDevice.Companion.toCastDevice +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.doAnswer +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@SmallTest +class CastDeviceTest : SysuiTestCase() { + private val mockAppInfo = + mock<ApplicationInfo>().apply { whenever(this.loadLabel(any())).thenReturn("") } + + private val packageManager = + mock<PackageManager>().apply { + whenever( + this.checkPermission( + Manifest.permission.REMOTE_DISPLAY_PROVIDER, + HEADLESS_REMOTE_PACKAGE, + ) + ) + .thenReturn(PackageManager.PERMISSION_GRANTED) + whenever( + this.checkPermission( + Manifest.permission.REMOTE_DISPLAY_PROVIDER, + NORMAL_PACKAGE, + ) + ) + .thenReturn(PackageManager.PERMISSION_DENIED) + + doAnswer { + // See Utils.isHeadlessRemoteDisplayProvider + if ((it.arguments[0] as Intent).`package` == HEADLESS_REMOTE_PACKAGE) { + emptyList() + } else { + listOf(mock<ResolveInfo>()) + } + } + .whenever(this) + .queryIntentActivities(any(), ArgumentMatchers.anyInt()) + + whenever(this.getApplicationInfo(any<String>(), any<Int>())).thenReturn(mockAppInfo) + } + + @Test + fun isCasting_disconnected_false() { + val device = + CastDevice( + id = "id", + name = "name", + state = CastDevice.CastState.Disconnected, + origin = CastDevice.CastOrigin.MediaRouter, + ) + + assertThat(device.isCasting).isFalse() + } + + @Test + fun isCasting_connecting_true() { + val device = + CastDevice( + id = "id", + name = "name", + state = CastDevice.CastState.Connecting, + origin = CastDevice.CastOrigin.MediaRouter, + ) + + assertThat(device.isCasting).isTrue() + } + + @Test + fun isCasting_connected_true() { + val device = + CastDevice( + id = "id", + name = "name", + state = CastDevice.CastState.Connected, + origin = CastDevice.CastOrigin.MediaRouter, + ) + + assertThat(device.isCasting).isTrue() + } + + @Test + fun routeToCastDevice_statusNone_stateDisconnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_NONE) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Disconnected) + } + + @Test + fun routeToCastDevice_statusNotAvailable_stateDisconnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Disconnected) + } + + @Test + fun routeToCastDevice_statusScanning_stateDisconnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_SCANNING) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Disconnected) + } + + @Test + fun routeToCastDevice_statusAvailable_isSelectedFalse_stateDisconnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_AVAILABLE) + whenever(this.isSelected).thenReturn(false) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Disconnected) + } + + @Test + fun routeToCastDevice_statusAvailable_isSelectedTrue_stateConnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_AVAILABLE) + whenever(this.isSelected).thenReturn(true) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Connected) + } + + @Test + fun routeToCastDevice_statusInUse_isSelectedFalse_stateDisconnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_IN_USE) + whenever(this.isSelected).thenReturn(false) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Disconnected) + } + + @Test + fun routeToCastDevice_statusInUse_isSelectedTrue_stateConnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_IN_USE) + whenever(this.isSelected).thenReturn(true) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Connected) + } + + @Test + fun routeToCastDevice_statusConnecting_isSelectedFalse_stateConnecting() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_CONNECTING) + whenever(this.isSelected).thenReturn(false) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Connecting) + } + + @Test + fun routeToCastDevice_statusConnecting_isSelectedTrue_stateConnecting() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_CONNECTING) + whenever(this.isSelected).thenReturn(true) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Connecting) + } + + @Test + fun routeToCastDevice_statusConnected_isSelectedFalse_stateConnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_CONNECTED) + whenever(this.isSelected).thenReturn(false) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Connected) + } + + @Test + fun routeToCastDevice_statusConnected_isSelectedTrue_stateConnected() { + val route = + mockRouteInfo().apply { + whenever(this.statusCode).thenReturn(MediaRouter.RouteInfo.STATUS_CONNECTED) + whenever(this.isSelected).thenReturn(true) + } + + val device = route.toCastDevice(context) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Connected) + } + + @Test + fun routeToCastDevice_tagIsStringType_idMatchesTag() { + val route = mock<MediaRouter.RouteInfo>().apply { whenever(this.tag).thenReturn("FakeTag") } + + val device = route.toCastDevice(context) + + assertThat(device.id).isEqualTo("FakeTag") + } + + @Test + fun routeToCastDevice_tagIsOtherType_idMatchesTag() { + val tag = listOf("tag1", "tag2") + val route = mock<MediaRouter.RouteInfo>().apply { whenever(this.tag).thenReturn(tag) } + + val device = route.toCastDevice(context) + + assertThat(device.id).isEqualTo(tag.toString()) + } + + @Test + fun routeToCastDevice_nameMatchesName() { + val route = mockRouteInfo().apply { whenever(this.getName(context)).thenReturn("FakeName") } + + val device = route.toCastDevice(context) + + assertThat(device.name).isEqualTo("FakeName") + } + + @Test + fun routeToCastDevice_descriptionMatchesDescription() { + val route = + mockRouteInfo().apply { whenever(this.description).thenReturn("FakeDescription") } + + val device = route.toCastDevice(context) + + assertThat(device.description).isEqualTo("FakeDescription") + } + + @Test + fun routeToCastDevice_tagIsRoute() { + val route = mockRouteInfo() + + val device = route.toCastDevice(context) + + assertThat(device.tag).isEqualTo(route) + } + + @Test + fun routeToCastDevice_originIsMediaRouter() { + val route = mockRouteInfo() + + val device = route.toCastDevice(context) + + assertThat(device.origin).isEqualTo(CastDevice.CastOrigin.MediaRouter) + } + + @Test + fun routeToCastDevice_nullValues_ok() { + val device = mockRouteInfo().toCastDevice(context) + + assertThat(device.name).isNull() + assertThat(device.description).isNull() + } + + @Test + fun projectionToCastDevice_idMatchesPackage() { + val projection = + mock<MediaProjectionInfo>().apply { + whenever(this.packageName).thenReturn("fake.package") + } + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.id).isEqualTo("fake.package") + } + + @Test + fun projectionToCastDevice_name_packageIsHeadlessRemote_isEmpty() { + val projection = + mock<MediaProjectionInfo>().apply { + whenever(this.packageName).thenReturn(HEADLESS_REMOTE_PACKAGE) + } + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.name).isEmpty() + } + + @Test + fun projectionToCastDevice_name_packageMissingFromPackageManager_isPackageName() { + val projection = + mock<MediaProjectionInfo>().apply { + whenever(this.packageName).thenReturn(NORMAL_PACKAGE) + } + + whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>())) + .thenThrow(PackageManager.NameNotFoundException()) + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.name).isEqualTo(NORMAL_PACKAGE) + } + + @Test + fun projectionToCastDevice_name_nameFromPackageManagerEmpty_isPackageName() { + val projection = + mock<MediaProjectionInfo>().apply { + whenever(this.packageName).thenReturn(NORMAL_PACKAGE) + } + + val appInfo = mock<ApplicationInfo>() + whenever(appInfo.loadLabel(packageManager)).thenReturn("") + whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>())) + .thenReturn(appInfo) + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.name).isEqualTo(NORMAL_PACKAGE) + } + + @Test + fun projectionToCastDevice_name_packageManagerHasName_isName() { + val projection = + mock<MediaProjectionInfo>().apply { + whenever(this.packageName).thenReturn(NORMAL_PACKAGE) + } + + val appInfo = mock<ApplicationInfo>() + whenever(appInfo.loadLabel(packageManager)).thenReturn("Valid App Name") + whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>())) + .thenReturn(appInfo) + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.name).isEqualTo("Valid App Name") + } + + @Test + fun projectionToCastDevice_descriptionIsCasting() { + val projection = mockProjectionInfo() + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.description).isEqualTo(context.getString(R.string.quick_settings_casting)) + } + + @Test + fun projectionToCastDevice_stateIsConnected() { + val projection = mockProjectionInfo() + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.state).isEqualTo(CastDevice.CastState.Connected) + } + + @Test + fun projectionToCastDevice_tagIsProjection() { + val projection = mockProjectionInfo() + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.tag).isEqualTo(projection) + } + + @Test + fun projectionToCastDevice_originIsMediaProjection() { + val projection = mockProjectionInfo() + + val device = projection.toCastDevice(context, packageManager) + + assertThat(device.origin).isEqualTo(CastDevice.CastOrigin.MediaProjection) + } + + private fun mockRouteInfo(): MediaRouter.RouteInfo { + return mock<MediaRouter.RouteInfo>().apply { whenever(this.tag).thenReturn(Any()) } + } + + private fun mockProjectionInfo(): MediaProjectionInfo { + return mock<MediaProjectionInfo>().apply { + whenever(this.packageName).thenReturn("fake.package") + } + } + + private companion object { + const val HEADLESS_REMOTE_PACKAGE = "headless.remote.package" + const val NORMAL_PACKAGE = "normal.package" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt index 22c72cc495b1..7549a7fa0499 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt @@ -16,12 +16,12 @@ package com.android.systemui.statusbar.policy -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View.MeasureSpec.UNSPECIFIED import android.view.View.MeasureSpec.makeMeasureSpec import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.widget.LinearLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Before @@ -31,7 +31,7 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ClockTest : SysuiTestCase() { private lateinit var clockView: Clock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt index c606511456fe..f871d2748fc9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.policy import android.hardware.devicestate.DeviceState import android.hardware.devicestate.DeviceStateManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableResources +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED @@ -44,7 +44,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class DevicePostureControllerImplTest : SysuiTestCase() { private val useBaseStateDeviceState = SUPPORTED_POSTURES_SIZE diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt index 31bd57e777d1..649a4ab2b968 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.policy import android.os.Handler import android.provider.Settings -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -45,7 +45,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class DeviceProvisionedControllerImplTest : SysuiTestCase() { @@ -246,4 +246,4 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { `when`(userTracker.userId).thenReturn(toUser) userTrackerCallbackCaptor.value.onUserChanged(toUser, mContext) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java index 683136d7af0f..f5efd6d5fdff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java @@ -23,10 +23,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Configuration; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -51,7 +51,7 @@ import org.mockito.MockitoAnnotations; import java.util.Map; import java.util.function.Consumer; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class ExtensionControllerImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java index 784fb71121d8..3721b0ed987a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java @@ -33,9 +33,9 @@ import android.net.TetheringManager; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.UserManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -57,7 +57,7 @@ import java.util.Collections; import java.util.concurrent.Executor; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class HotspotControllerImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index 1250228e2d37..b7a35158fdf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -16,12 +16,12 @@ package com.android.systemui.statusbar.policy -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.res.R @@ -45,7 +45,7 @@ import org.mockito.MockitoAnnotations @SmallTest @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { @Mock private lateinit var userSwitcherController: UserSwitcherController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java index 3a086aec98ad..aed9af6df454 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -25,9 +25,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.hardware.biometrics.BiometricSourceType; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.widget.LockPatternUtils; @@ -54,7 +54,7 @@ import java.util.Random; @SmallTest @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class KeyguardStateControllerTest extends SysuiTestCase { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt index 635d63ed5eb2..750f0c68bf3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt @@ -15,8 +15,8 @@ package com.android.systemui.statusbar.policy import android.app.StatusBarManager import android.content.res.Configuration -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R @@ -35,7 +35,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class RemoteInputQuickSettingsDisablerTest : SysuiTestCase() { @@ -134,4 +134,4 @@ class RemoteInputQuickSettingsDisablerTest : SysuiTestCase() { private fun shouldDisableQs(state: Int): Boolean { return state and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0 } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java index a9681e0a03f0..36e305244d18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java @@ -22,10 +22,10 @@ import static junit.framework.Assert.assertTrue; import android.app.RemoteInput; import android.provider.DeviceConfig; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -39,7 +39,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class SmartReplyConstantsTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java index ddd29c3f2803..637a0f16cd5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java @@ -27,10 +27,10 @@ import android.app.NotificationManager; import android.os.Handler; import android.provider.Settings; import android.service.notification.ZenModeConfig; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -51,7 +51,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class ZenModeControllerImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt index 6f40f15ca00a..94f0d196fa21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt @@ -15,6 +15,7 @@ package com.android.systemui.statusbar.policy.bluetooth import android.bluetooth.BluetoothProfile +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothAdapter @@ -30,11 +31,13 @@ import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.TestScope import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class BluetoothRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: BluetoothRepositoryImpl diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt deleted file mode 100644 index 004f67934e86..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.systemui.statusbar.policy.data.repository - -import android.app.NotificationManager -import android.provider.Settings -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.statusbar.policy.ZenModeController -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.mockito.withArgCaptor -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidJUnit4::class) -class ZenModeRepositoryImplTest : SysuiTestCase() { - @Mock lateinit var zenModeController: ZenModeController - - lateinit var underTest: ZenModeRepositoryImpl - - private val testPolicy = NotificationManager.Policy(0, 1, 0) - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - underTest = ZenModeRepositoryImpl(zenModeController) - } - - @Test - fun zenMode_reflectsCurrentControllerState() = runTest { - whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) - val zenMode by collectLastValue(underTest.zenMode) - assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) - } - - @Test - fun zenMode_updatesWhenControllerStateChanges() = runTest { - val zenMode by collectLastValue(underTest.zenMode) - runCurrent() - whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) } - .onZenChanged(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) - } - - @Test - fun policy_reflectsCurrentControllerState() { - runTest { - whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy) - val policy by collectLastValue(underTest.consolidatedNotificationPolicy) - assertThat(policy).isEqualTo(testPolicy) - } - } - - @Test - fun policy_updatesWhenControllerStateChanges() = runTest { - val policy by collectLastValue(underTest.consolidatedNotificationPolicy) - runCurrent() - whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy) - withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) } - .onConsolidatedPolicyChanged(testPolicy) - assertThat(policy).isEqualTo(testPolicy) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt deleted file mode 100644 index dbb106264ecd..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.policy.domain.interactor - -import android.app.NotificationManager.Policy -import android.provider.Settings -import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule -import com.android.systemui.SysuiTestCase -import com.android.systemui.collectLastValue -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository -import com.android.systemui.user.domain.UserDomainLayerModule -import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component -import org.junit.Test - -@SmallTest -class ZenModeInteractorTest : SysuiTestCase() { - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<ZenModeInteractor> { - - val repository: FakeZenModeRepository - - @Component.Factory - interface Factory { - fun create(@BindsInstance test: SysuiTestCase): TestComponent - } - } - - private val testComponent: TestComponent = - DaggerZenModeInteractorTest_TestComponent.factory().create(test = this) - - @Test - fun testIsZenModeEnabled_off() = - testComponent.runTest { - val enabled by collectLastValue(underTest.isZenModeEnabled) - - repository.zenMode.value = Settings.Global.ZEN_MODE_OFF - runCurrent() - - assertThat(enabled).isFalse() - } - - @Test - fun testIsZenModeEnabled_alarms() = - testComponent.runTest { - val enabled by collectLastValue(underTest.isZenModeEnabled) - - repository.zenMode.value = Settings.Global.ZEN_MODE_ALARMS - runCurrent() - - assertThat(enabled).isTrue() - } - - @Test - fun testIsZenModeEnabled_importantInterruptions() = - testComponent.runTest { - val enabled by collectLastValue(underTest.isZenModeEnabled) - - repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - runCurrent() - - assertThat(enabled).isTrue() - } - - @Test - fun testIsZenModeEnabled_noInterruptions() = - testComponent.runTest { - val enabled by collectLastValue(underTest.isZenModeEnabled) - - repository.zenMode.value = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS - runCurrent() - - assertThat(enabled).isTrue() - } - - @Test - fun testIsZenModeEnabled_unknown() = - testComponent.runTest { - val enabled by collectLastValue(underTest.isZenModeEnabled) - - repository.zenMode.value = 4 // this should fail if we ever add another zen mode type - runCurrent() - - assertThat(enabled).isFalse() - } - - @Test - fun testAreNotificationsHiddenInShade_noPolicy() = - testComponent.runTest { - val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - - repository.consolidatedNotificationPolicy.value = null - repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - runCurrent() - - assertThat(hidden).isFalse() - } - - @Test - fun testAreNotificationsHiddenInShade_zenOffShadeSuppressed() = - testComponent.runTest { - val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - - repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) - repository.zenMode.value = Settings.Global.ZEN_MODE_OFF - runCurrent() - - assertThat(hidden).isFalse() - } - - @Test - fun testAreNotificationsHiddenInShade_zenOnShadeNotSuppressed() = - testComponent.runTest { - val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - - repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR) - repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - runCurrent() - - assertThat(hidden).isFalse() - } - - @Test - fun testAreNotificationsHiddenInShade_zenOnShadeSuppressed() = - testComponent.runTest { - val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - - repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) - repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - runCurrent() - - assertThat(hidden).isTrue() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt index 8576d4f56906..03a25e3d9ce3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt @@ -21,18 +21,21 @@ import android.app.StatusBarManager.WINDOW_NAVIGATION_BAR import android.app.StatusBarManager.WINDOW_STATE_HIDDEN import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class StatusBarWindowStateControllerTest : SysuiTestCase() { private lateinit var controller: StatusBarWindowStateController private lateinit var callback: CommandQueue.Callbacks diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt index 16132ba7cff5..32ef501cd9e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.surfaceeffects.glowboxeffect import android.graphics.Color import android.graphics.Paint -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule @@ -31,7 +31,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class GlowBoxEffectTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt index 41d7fd54902d..67a42196d2e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.surfaceeffects.loadingeffect import android.graphics.Paint import android.graphics.RenderEffect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule @@ -33,7 +33,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class LoadingEffectTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt index 0d19ab1db390..a5afe1e33169 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.surfaceeffects.ripple import android.graphics.Color -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.surfaceeffects.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER @@ -29,7 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MultiRippleControllerTest : SysuiTestCase() { private lateinit var multiRippleController: MultiRippleController private lateinit var multiRippleView: MultiRippleView diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt index 74ed7fb2c699..8dafa824cb8c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.surfaceeffects.ripple import android.graphics.Color -import android.testing.AndroidTestingRunner import androidx.core.graphics.ColorUtils +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class RippleAnimationTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt index 89cc18cc5d67..2fee2a4553b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.surfaceeffects.ripple -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -24,7 +24,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class RippleShaderTest : SysuiTestCase() { private lateinit var rippleShader: RippleShader diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt index 1e5ab7e25599..18644ab67351 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.surfaceeffects.ripple -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Before @@ -23,7 +23,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class RippleViewTest : SysuiTestCase() { private lateinit var rippleView: RippleView diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt index f1fadf6f1a4a..fbd3eae59046 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt @@ -16,14 +16,14 @@ package com.android.systemui.surfaceeffects.shaders import android.graphics.Color -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SolidColorShaderTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt index 64ea6a68a518..d32bffbdff1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt @@ -16,14 +16,14 @@ package com.android.systemui.surfaceeffects.shaders import android.graphics.Color -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SparkleShaderTest : SysuiTestCase() { private lateinit var sparkleShader: SparkleShader diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt index 08b49f026523..4ba64384a186 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.surfaceeffects.turbulencenoise import android.graphics.Color -import android.testing.AndroidTestingRunner import android.view.View.INVISIBLE import android.view.View.VISIBLE +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController.Companion.AnimationState.EASE_IN @@ -33,7 +33,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class TurbulenceNoiseControllerTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() // FakeExecutor is needed to run animator. diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt index e62ca645d772..286f01736497 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.surfaceeffects.turbulencenoise -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE @@ -25,7 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class TurbulenceNoiseShaderTest : SysuiTestCase() { private lateinit var turbulenceNoiseShader: TurbulenceNoiseShader diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt index 953071cb0a02..e09d4ea9bcc6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.surfaceeffects.turbulencenoise import android.graphics.Color -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class TurbulenceNoiseViewTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index ae4dfbd6ab31..bb6ba46f1a0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake @@ -44,6 +45,7 @@ import com.android.systemui.util.wakelock.WakeLockFake import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.reset @@ -53,6 +55,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class TemporaryViewDisplayControllerTest : SysuiTestCase() { private lateinit var underTest: TestController diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt index 38c1a785d1e9..1dfd9343d185 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.temporarydisplay +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase @@ -28,9 +29,11 @@ import java.io.PrintWriter import java.io.StringWriter import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito @SmallTest +@RunWith(AndroidJUnit4::class) class TemporaryViewLoggerTest : SysuiTestCase() { private lateinit var buffer: LogBuffer private lateinit var logger: TemporaryViewLogger<TemporaryViewInfo> diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt index f707a8dafeda..281ecfb473cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.temporarydisplay +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake @@ -23,8 +24,10 @@ import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class TemporaryViewUiEventLoggerTest : SysuiTestCase() { private lateinit var uiEventLoggerFake: UiEventLoggerFake private lateinit var logger: TemporaryViewUiEventLogger diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt index 7586fe48b308..a230f0630d6e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt @@ -19,12 +19,14 @@ package com.android.systemui.temporarydisplay import android.graphics.Rect import android.view.View import android.view.ViewTreeObserver +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.verify @@ -32,6 +34,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidJUnit4::class) class TouchableRegionViewControllerTest : SysuiTestCase() { @Mock private lateinit var view: View diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt index c539246d4902..75d031dcaf38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.temporarydisplay.chipbar import android.graphics.Rect import android.view.MotionEvent import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer import com.android.systemui.SysuiTestCase @@ -29,8 +30,10 @@ import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class SwipeChipbarAwayGestureHandlerTest : SysuiTestCase() { private lateinit var underTest: SwipeChipbarAwayGestureHandler diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/TouchpadTutorialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/TouchpadTutorialViewModelTest.kt new file mode 100644 index 000000000000..2300a1f6b21e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/TouchpadTutorialViewModelTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.touchpad.tutorial.ui + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.model.sysUiState +import com.android.systemui.settings.displayTracker +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED +import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags +import com.android.systemui.testKosmos +import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TouchpadTutorialViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = TestScope() + private val sysUiState = kosmos.sysUiState + private val viewModel = + TouchpadTutorialViewModel( + TouchpadGesturesInteractor(sysUiState, kosmos.displayTracker, testScope.backgroundScope) + ) + + @Test + fun sysUiStateFlag_disabledByDefault() = + testScope.runTest { + assertThat(isFlagEnabled(SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED)).isFalse() + } + + @Test + fun sysUiStateFlag_enabledAfterTutorialOpened() = + testScope.runTest { + viewModel.onOpened() + + assertThat(isFlagEnabled(SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED)).isTrue() + } + + @Test + fun sysUiStateFlag_disabledAfterTutorialClosed() = + testScope.runTest { + viewModel.onOpened() + viewModel.onClosed() + + assertThat(isFlagEnabled(SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED)).isFalse() + } + + private fun TestScope.isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean { + // sysui state is changed on background scope so let's make sure it's executed + runCurrent() + return sysUiState.isFlagEnabled(flag) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java b/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java index 683397e4190e..bb7b31b95f5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java @@ -26,14 +26,17 @@ import android.util.DisplayMetrics; import android.view.View; import android.view.WindowManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; @SmallTest +@RunWith(AndroidJUnit4.class) public class TunablePaddingTest extends LeakCheckedTest { private static final String KEY = "KEY"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt index 2cdc8d8ffc84..5d850d82d806 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.unfold import android.content.Context import android.content.res.Resources -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -65,7 +65,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { private lateinit var displaySwitchLatencyTracker: DisplaySwitchLatencyTracker diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt index e1797c445a70..c29b86c1fa37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.unfold import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener import android.os.PowerManager -import android.testing.AndroidTestingRunner import android.view.ViewGroup import android.view.ViewTreeObserver +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.systemui.SysuiTestCase @@ -56,7 +56,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class FoldAodAnimationControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt index dddc71216da1..266a60dfb6e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt @@ -18,11 +18,14 @@ package com.android.systemui.unfold import android.os.PowerManager import android.os.SystemProperties -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.foldables.FoldLockSettingAvailabilityProvider +import com.android.internal.jank.Cuj.CUJ_FOLD_ANIM +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState import com.android.systemui.display.data.repository.fakeDeviceStateRepository import com.android.systemui.kosmos.Kosmos @@ -32,27 +35,34 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.power.shared.model.ScreenPowerState +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.atLeast import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { + @get:Rule val animatorTestRule = AnimatorTestRule(this) + private val kosmos = Kosmos() private val testScope: TestScope = kosmos.testScope private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository @@ -63,6 +73,8 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { private val mockFoldLockSettingAvailabilityProvider = mock<FoldLockSettingAvailabilityProvider>() private val onOverlayReady = mock<Runnable>() + private val mockJankMonitor = mock<InteractionJankMonitor>() + private val mockScrimView = mock<LightRevealScrim>() private lateinit var foldLightRevealAnimation: FoldLightRevealOverlayAnimation @Before @@ -71,6 +83,8 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { whenever(mockFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable) .thenReturn(true) fakeAnimationStatusRepository.onAnimationStatusChanged(true) + whenever(mockFullScreenController.scrimView).thenReturn(mockScrimView) + whenever(mockJankMonitor.begin(any(), eq(CUJ_FOLD_ANIM))).thenReturn(true) foldLightRevealAnimation = FoldLightRevealOverlayAnimation( @@ -80,7 +94,8 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.backgroundScope, fakeAnimationStatusRepository, mockControllerFactory, - mockFoldLockSettingAvailabilityProvider + mockFoldLockSettingAvailabilityProvider, + mockJankMonitor ) foldLightRevealAnimation.init() } @@ -99,9 +114,9 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() emitLastWakefulnessEventStartingToSleep() - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) turnScreenOn() - advanceTimeBy(ANIMATION_DURATION) + advanceTime(ANIMATION_DURATION) verifyFoldAnimationDidNotPlay() } @@ -111,8 +126,8 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() emitLastWakefulnessEventStartingToSleep() - advanceTimeBy(SHORT_DELAY_MS) - advanceTimeBy(ANIMATION_DURATION) + advanceTime(SHORT_DELAY_MS) + advanceTime(ANIMATION_DURATION) verifyFoldAnimationDidNotPlay() } @@ -122,10 +137,10 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() foldLightRevealAnimation.onScreenTurningOn {} - advanceTimeBy(WAIT_FOR_ANIMATION_TIMEOUT_MS) + advanceTime(WAIT_FOR_ANIMATION_TIMEOUT_MS) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) - advanceTimeBy(SHORT_DELAY_MS) - advanceTimeBy(ANIMATION_DURATION) + advanceTime(SHORT_DELAY_MS) + advanceTime(ANIMATION_DURATION) verifyFoldAnimationDidNotPlay() } @@ -135,10 +150,12 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() foldLightRevealAnimation.onScreenTurningOn {} - advanceTimeBy(SHORT_DELAY_MS) - advanceTimeBy(ANIMATION_DURATION) + advanceTime(SHORT_DELAY_MS) + clearInvocations(mockFullScreenController) + + // Unfold the device fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) verifyOverlayWasRemoved() @@ -149,12 +166,38 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() turnScreenOn() - advanceTimeBy(ANIMATION_DURATION) + clearInvocations(mockFullScreenController) + advanceTime(ANIMATION_DURATION) verifyOverlayWasRemoved() } @Test + fun foldToScreenOn_jankCujIsStarted() = + testScope.runTest { + foldDeviceToScreenOff() + turnScreenOn() + // Cuj has started but not ended + verify(mockJankMonitor, times(1)).begin(any(), eq(CUJ_FOLD_ANIM)) + verify(mockJankMonitor, never()).end(eq(CUJ_FOLD_ANIM)) + } + + @Test + fun foldToScreenOn_animationFinished_jankCujIsFinished() = + testScope.runTest { + foldDeviceToScreenOff() + turnScreenOn() + + advanceTime(ANIMATION_DURATION) + verify(mockJankMonitor, times(1)).end(eq(CUJ_FOLD_ANIM)) + } + + private fun TestScope.advanceTime(timeMs: Long) { + animatorTestRule.advanceTimeBy(timeMs) + advanceTimeBy(timeMs) + } + + @Test fun unfold_immediatelyRunRunnable() = testScope.runTest { foldLightRevealAnimation.onScreenTurningOn(onOverlayReady) @@ -165,18 +208,18 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { private suspend fun TestScope.foldDeviceToScreenOff() { fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) fakeDeviceStateRepository.emit(DeviceState.FOLDED) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_OFF) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) } private fun TestScope.turnScreenOn() { foldLightRevealAnimation.onScreenTurningOn {} - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) } private fun emitLastWakefulnessEventStartingToSleep() = @@ -195,6 +238,7 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L val ANIMATION_DURATION: Long get() = SystemProperties.getLong("persist.fold_animation_duration", 200L) + const val SHORT_DELAY_MS = 50L } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt index 39e4e6446d02..459b3e0dd0fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.unfold -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.FoldStateLoggingProvider.FoldStateLoggingListener @@ -34,7 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class FoldStateLoggingProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt index ab779a75518e..448b63e88b60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.unfold -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -38,7 +38,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class FoldStateRepositoryTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt index 06f1a8848fad..208e39c7f255 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.unfold import android.os.VibrationAttributes import android.os.VibrationEffect import android.os.vibrator -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.testKosmos @@ -30,7 +30,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class UnfoldHapticsPlayerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt index 2955384c0bbd..1e5929daf9c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.unfold import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener import android.provider.Settings -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.systemui.SysuiTestCase @@ -39,7 +39,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class UnfoldLatencyTrackerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt index 0c452eb9d461..b5282eb7ab03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt @@ -1,6 +1,6 @@ package com.android.systemui.unfold -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.WallpaperController @@ -13,7 +13,7 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt index 3c539979c527..b48a6da3eee7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt @@ -14,7 +14,7 @@ */ package com.android.systemui.unfold.config -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -28,7 +28,7 @@ import org.junit.runner.RunWith * Internal Android resource constants are not available in public APIs, * so we can't use them there directly. */ -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ResourceUnfoldTransitionConfigTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt index e5f619b299a1..61f0abb0e476 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.unfold.progress import android.os.Handler import android.os.HandlerThread import android.os.Looper -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.FakeUnfoldTransitionProvider @@ -29,7 +29,7 @@ import kotlin.test.Test import kotlinx.coroutines.test.runTest import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @RunWithLooper(setAsMainLooper = true) class MainThreadUnfoldTransitionProgressProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt index 14fb054a1d9d..97688d55b778 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.unfold.progress import android.os.Handler import android.os.HandlerThread -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.UnfoldTransitionProgressProvider @@ -33,7 +33,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt index 4989a21791fb..a7b67e21ca4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.unfold.progress -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.SysuiTestCase @@ -24,7 +24,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt index 70eadcee7607..b93c161a7039 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.unfold.progress -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.SysuiTestCase @@ -25,7 +25,7 @@ import kotlin.time.Duration.Companion.milliseconds import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class UnfoldRemoteFilterTest : SysuiTestCase() { private val listener = TestUnfoldProgressListener() diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 552b60cbeb21..21a45ecb5922 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -21,8 +21,8 @@ import android.content.res.Configuration import android.content.res.Resources import android.os.Handler import android.os.Looper -import android.testing.AndroidTestingRunner import androidx.core.util.Consumer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig @@ -50,7 +50,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DeviceFoldStateProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt index 4eb159103b49..4d9c80fe7295 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.unfold.updates -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -32,7 +32,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DeviceStateRepositoryTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt index eaef0073b6fb..f5bcc21056e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt @@ -21,9 +21,9 @@ import android.hardware.display.DisplayManager import android.os.HandlerThread import android.os.Looper import android.os.Process -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener @@ -41,7 +41,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @RunWithLooper class RotationChangeProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt index 70ec050afe1d..8d892ed8efbe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt @@ -15,9 +15,9 @@ */ package com.android.systemui.unfold.util -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Surface +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.FakeUnfoldTransitionProvider @@ -36,7 +36,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt index 451bd24dd83f..39977c790080 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.unfold.util import android.content.ContentResolver import android.database.ContentObserver import android.provider.Settings -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.FakeUnfoldTransitionProvider @@ -35,7 +35,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt index 4486402f43ec..6e0bff21e2df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.unfold.util import android.os.Handler import android.os.HandlerThread import android.os.Process -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.FakeUnfoldTransitionProvider @@ -38,7 +38,7 @@ import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @RunWithLooper class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt index cd4d7b54916e..0cbe1bafbcb7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.unfold.util -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.FakeUnfoldTransitionProvider @@ -26,7 +26,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class UnfoldOnlyProgressProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt index b04eb01201fa..32c598612aa6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt @@ -20,9 +20,9 @@ import android.content.Intent import android.hardware.usb.IUsbSerialReader import android.hardware.usb.UsbAccessory import android.hardware.usb.UsbManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule import com.android.systemui.SysuiTestCase @@ -40,7 +40,7 @@ import org.junit.runner.RunWith /** * UsbPermissionActivityTest */ -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class UsbPermissionActivityTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt index 6db35ae94a3f..84cd79d9c8dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.user import android.app.Dialog -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any @@ -15,7 +15,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class CreateUserActivityTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt index 35f9c417abd3..a29114007895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt @@ -3,8 +3,8 @@ package com.android.systemui.user import android.content.pm.UserInfo import android.graphics.Bitmap import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -22,7 +22,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class UserCreatorTest : SysuiTestCase() { companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 0669cb8b8ba5..37a73cf62929 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -21,6 +21,7 @@ import android.content.pm.UserInfo import android.os.UserHandle import android.os.UserManager import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeUserTracker @@ -41,7 +42,6 @@ import kotlinx.coroutines.test.TestCoroutineScope import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.`when` as whenever @@ -49,7 +49,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class UserRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var manager: UserManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt index 01795e92d141..20b273ae7f4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt @@ -22,6 +22,7 @@ import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.GuestResetOrExitSessionReceiver @@ -39,7 +40,6 @@ import kotlinx.coroutines.test.TestCoroutineScope import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.never @@ -47,7 +47,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class GuestUserInteractorTest : SysuiTestCase() { @Mock private lateinit var manager: UserManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt index b30f77a663af..59ad38a87146 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.user.domain.interactor +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.user.data.repository.FakeUserRepository @@ -26,11 +27,10 @@ import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.MockitoAnnotations @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class RefreshUsersSchedulerTest : SysuiTestCase() { private lateinit var underTest: RefreshUsersScheduler diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt index 140e919d613f..78028f819fa0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt @@ -1,6 +1,7 @@ package com.android.systemui.user.domain.interactor import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER import com.android.systemui.SysuiTestCase @@ -10,10 +11,9 @@ import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class SelectedUserInteractorTest : SysuiTestCase() { private lateinit var underTest: SelectedUserInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index 96c6eb8107df..ccbdffa03cbf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -28,6 +28,7 @@ import android.os.Process import android.os.UserHandle import android.os.UserManager import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor @@ -75,7 +76,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt @@ -89,7 +89,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class UserSwitcherInteractorTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index 661837bdb1e4..99cdf731a765 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -23,6 +23,7 @@ import android.content.pm.UserInfo import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor @@ -60,7 +61,6 @@ import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.doAnswer @@ -68,7 +68,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class StatusBarUserChipViewModelTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var activityManager: ActivityManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 5661e202d134..917df619467d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -21,6 +21,7 @@ import android.app.ActivityManager import android.app.admin.DevicePolicyManager import android.content.pm.UserInfo import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor @@ -62,13 +63,12 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class UserSwitcherViewModelTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt index 8bfff9c209e2..b3f2113f86ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.util.kotlin import android.content.ComponentName import android.content.pm.ComponentInfo import android.content.pm.PackageManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java index df08efaf16d2..c896fc0bfb8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java @@ -23,8 +23,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.hardware.SensorEventListener; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -39,7 +39,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class AsyncSensorManagerTest extends SysuiTestCase { private AsyncSensorManager mAsyncSensorManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt index eb11e383d519..ef8d51a23dc2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt @@ -22,8 +22,8 @@ import android.net.Uri import android.os.Handler import android.os.Looper import android.provider.Settings.SettingNotFoundException -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.kotlin.eq /** Tests for [SettingsProxy]. */ -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class SettingsProxyTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt index 38469ee7955d..c08ca7d1bdc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt @@ -23,8 +23,8 @@ import android.net.Uri import android.os.Handler import android.os.Looper import android.provider.Settings -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeUserTracker @@ -39,7 +39,7 @@ import org.mockito.Mockito.verify import org.mockito.kotlin.eq /** Tests for [UserSettingsProxy]. */ -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class UserSettingsProxyTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 4654a4dd8fce..6efb7d8fc3bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -35,6 +35,7 @@ import static org.junit.Assume.assumeNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; @@ -783,8 +784,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.show(SHOW_REASON_UNKNOWN); mTestableLooper.processAllMessages(); - verify(mVolumeDialogInteractor).onDialogShown(); - verify(mVolumeDialogInteractor).onDialogDismissed(); // dismiss by timeout + verify(mVolumeDialogInteractor, atLeastOnce()).onDialogShown(); + verify(mVolumeDialogInteractor, atLeastOnce()).onDialogDismissed(); // dismiss by timeout } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt index 7d89a559f12f..bdecf2bdb53d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt @@ -20,6 +20,7 @@ import android.app.WallpaperInfo import android.app.WallpaperManager import android.content.Intent import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -37,9 +38,11 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) class WallpaperRepositoryImplTest : SysuiTestCase() { private val testDispatcher = StandardTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index fabb9b770a11..c5fbc39e79c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -25,6 +25,8 @@ import static android.service.notification.NotificationListenerService.NOTIFICAT import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; +import static androidx.test.ext.truth.content.IntentSubject.assertThat; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING; import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR; @@ -2017,6 +2019,31 @@ public class BubblesTest extends SysuiTestCase { } @Test + public void testShowOrHideAppBubble_updateExistedBubbleInOverflow_updateIntentInBubble() { + String appBubbleKey = Bubble.getAppBubbleKeyForApp(mAppBubbleIntent.getPackage(), mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); + // Collapse the stack so we don't need to wait for the dismiss animation in the test + mBubbleController.collapseStack(); + // Dismiss the app bubble so it's in the overflow + mBubbleController.dismissBubble(appBubbleKey, Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNotNull(); + + // Modify the intent to include new extras. + Intent newAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class) + .setPackage(mContext.getPackageName()) + .putExtra("hello", "world"); + + // Calling this while collapsed will re-add and expand the app bubble + mBubbleController.showOrHideAppBubble(newAppBubbleIntent, mUser0, mAppBubbleIcon); + assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleKey); + assertThat(mBubbleController.isStackExpanded()).isTrue(); + assertThat(mBubbleData.getBubbles().size()).isEqualTo(1); + assertThat(mBubbleData.getBubbles().get(0).getAppBubbleIntent()).extras().string( + "hello").isEqualTo("world"); + assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull(); + } + + @Test public void testCreateBubbleFromOngoingNotification() { NotificationEntry notif = new NotificationEntryBuilder() .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true) diff --git a/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt index 21dea6b1cbd0..2ee289bd026d 100644 --- a/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt @@ -18,6 +18,8 @@ package android.view import android.content.applicationContext import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock var Kosmos.layoutInflater: LayoutInflater by Kosmos.Fixture { LayoutInflater.from(applicationContext) } +var Kosmos.mockedLayoutInflater: LayoutInflater by Kosmos.Fixture { mock<LayoutInflater>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 8eef930cbb0c..78d8abeb477a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -21,7 +21,6 @@ import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.content.Context; import android.content.res.Resources; -import android.os.Build; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; @@ -297,7 +296,7 @@ public abstract class SysuiTestCase { } public static boolean isRobolectricTest() { - return !isRavenwoodTest() && Build.FINGERPRINT.contains("robolectric"); + return !isRavenwoodTest() && !System.getProperty("java.vm.name").equals("Dalvik"); } protected boolean isScreenshotTest() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt index 9dae44dc8053..5db9d3106b8a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt @@ -16,6 +16,7 @@ package com.android.systemui import android.app.ActivityManager +import android.app.DreamManager import android.app.admin.DevicePolicyManager import android.app.trust.TrustManager import android.os.UserManager @@ -32,6 +33,7 @@ import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.biometrics.AuthController import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.ScreenLifecycle @@ -93,6 +95,7 @@ data class TestMocksModule( @get:Provides val demoModeController: DemoModeController = mock(), @get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(), @get:Provides val dozeParameters: DozeParameters = mock(), + @get:Provides val dreamManager: DreamManager = mock(), @get:Provides val dumpManager: DumpManager = mock(), @get:Provides val headsUpManager: HeadsUpManager = mock(), @get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(), @@ -130,6 +133,7 @@ data class TestMocksModule( @get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(), @get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(), @get:Provides val communalInteractor: CommunalInteractor = mock(), + @get:Provides val communalSceneInteractor: CommunalSceneInteractor = mock(), @get:Provides val sceneLogger: SceneLogger = mock(), @get:Provides val trustManager: TrustManager = mock(), @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt index f75cdd4d3bbc..9236bd2a2a8b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor @@ -28,6 +29,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.statusbar.policy.keyguardStateController +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.systemClock val Kosmos.alternateBouncerInteractor: AlternateBouncerInteractor by @@ -47,3 +49,24 @@ val Kosmos.alternateBouncerInteractor: AlternateBouncerInteractor by sceneInteractor = { sceneInteractor }, ) } + +fun Kosmos.givenCanShowAlternateBouncer() { + this.givenAlternateBouncerSupported() + this.keyguardBouncerRepository.setPrimaryShow(false) + this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + this.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) + whenever(this.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) + whenever(this.keyguardStateController.isUnlocked).thenReturn(false) +} + +fun Kosmos.givenAlternateBouncerSupported() { + if (DeviceEntryUdfpsRefactor.isEnabled) { + this.fingerprintPropertyRepository.supportsUdfps() + } else { + this.keyguardBouncerRepository.setAlternateBouncerUIAvailable(true) + } +} + +fun Kosmos.givenCannotShowAlternateBouncer() { + this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt index d3ed58bf5be0..1da1fb2ee52f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt @@ -17,16 +17,28 @@ package com.android.systemui.communal.data.repository +import android.content.pm.UserInfo import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map /** Fake implementation of [CommunalPrefsRepository] */ class FakeCommunalPrefsRepository : CommunalPrefsRepository { - private val _isCtaDismissed = MutableStateFlow(false) - override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow() + private val _isCtaDismissed = MutableStateFlow<Set<UserInfo>>(emptySet()) + private val _isDisclaimerDismissed = MutableStateFlow<Set<UserInfo>>(emptySet()) - override suspend fun setCtaDismissedForCurrentUser() { - _isCtaDismissed.value = true + override fun isCtaDismissed(user: UserInfo): Flow<Boolean> = + _isCtaDismissed.map { it.contains(user) } + + override fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> = + _isDisclaimerDismissed.map { it.contains(user) } + + override suspend fun setCtaDismissed(user: UserInfo) { + _isCtaDismissed.value = _isCtaDismissed.value.toMutableSet().apply { add(user) } + } + + override suspend fun setDisclaimerDismissed(user: UserInfo) { + _isDisclaimerDismissed.value = + _isDisclaimerDismissed.value.toMutableSet().apply { add(user) } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 1583d1c5d83f..b58861b1104e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -19,7 +19,6 @@ package com.android.systemui.communal.domain.interactor import android.os.userManager import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.repository.communalMediaRepository -import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.flags.Flags @@ -46,7 +45,7 @@ val Kosmos.communalInteractor by Fixture { broadcastDispatcher = broadcastDispatcher, communalSceneInteractor = communalSceneInteractor, widgetRepository = communalWidgetRepository, - communalPrefsRepository = communalPrefsRepository, + communalPrefsInteractor = communalPrefsInteractor, mediaRepository = communalMediaRepository, smartspaceRepository = smartspaceRepository, keyguardInteractor = keyguardInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorKosmos.kt new file mode 100644 index 000000000000..37563c4d4eeb --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorKosmos.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.communal.domain.interactor + +import com.android.systemui.communal.data.repository.communalPrefsRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.settings.userTracker +import com.android.systemui.user.domain.interactor.selectedUserInteractor +import com.android.systemui.util.mockito.mock + +var Kosmos.communalPrefsInteractor: CommunalPrefsInteractor by + Kosmos.Fixture { + CommunalPrefsInteractor( + applicationCoroutineScope, + communalPrefsRepository, + selectedUserInteractor, + userTracker, + tableLogBuffer = mock() + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryKosmos.kt new file mode 100644 index 000000000000..e4efadbd80e3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/data/repository/DeviceConfigRepositoryKosmos.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.deviceconfig.data.repository + +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.util.deviceConfigProxy + +val Kosmos.deviceConfigRepository by Fixture { + DeviceConfigRepository( + backgroundExecutor = fakeExecutor, + backgroundDispatcher = testDispatcher, + dataSource = deviceConfigProxy, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorKosmos.kt new file mode 100644 index 000000000000..d53849771eb0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceconfig/domain/interactor/DeviceConfigInteractorKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.deviceconfig.domain.interactor + +import com.android.systemui.deviceconfig.data.repository.deviceConfigRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.deviceConfigInteractor by Fixture { + DeviceConfigInteractor( + repository = deviceConfigRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 2d100f0dd5a8..dca531ac3cc6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -119,6 +119,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _keyguardAlpha = MutableStateFlow(1f) override val keyguardAlpha: StateFlow<Float> = _keyguardAlpha + override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f) + override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null) override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false) @@ -259,6 +261,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _keyguardAlpha.value = alpha } + override fun setPanelAlpha(alpha: Float) { + panelAlpha.value = alpha + } + fun setIsEncryptedOrLockdown(value: Boolean) { _isEncryptedOrLockdown.value = value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt index cd83c2fa480c..39a1a639ced9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt @@ -33,9 +33,10 @@ class FakeTrustRepository @Inject constructor() : TrustRepository { private val _isTrustUsuallyManaged = MutableStateFlow(false) override val isCurrentUserTrustUsuallyManaged: StateFlow<Boolean> get() = _isTrustUsuallyManaged + private val _isCurrentUserTrusted = MutableStateFlow(false) - override val isCurrentUserTrusted: Flow<Boolean> - get() = _isCurrentUserTrusted + override val isCurrentUserTrusted: StateFlow<Boolean> + get() = _isCurrentUserTrusted.asStateFlow() private val _isCurrentUserActiveUnlockAvailable = MutableStateFlow(false) override val isCurrentUserActiveUnlockRunning: StateFlow<Boolean> = @@ -48,6 +49,13 @@ class FakeTrustRepository @Inject constructor() : TrustRepository { private val _requestDismissKeyguard = MutableStateFlow(TrustModel(false, 0, TrustGrantFlags(0))) override val trustAgentRequestingToDismissKeyguard: Flow<TrustModel> = _requestDismissKeyguard + var keyguardShowingChangeEventCount: Int = 0 + private set + + override suspend fun reportKeyguardShowingChanged() { + keyguardShowingChangeEventCount++ + } + fun setCurrentUserTrusted(trust: Boolean) { _isCurrentUserTrusted.value = trust } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt index edf77a007efd..744b1275d672 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt @@ -16,7 +16,9 @@ package com.android.systemui.keyguard.domain.interactor +import android.service.dream.dreamManager import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.deviceentry.data.repository.deviceEntryRepository import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos @@ -35,8 +37,10 @@ var Kosmos.fromDozingTransitionInteractor by mainDispatcher = testDispatcher, keyguardInteractor = keyguardInteractor, communalInteractor = communalInteractor, + communalSceneInteractor = communalSceneInteractor, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, deviceEntryRepository = deviceEntryRepository, + dreamManager = dreamManager ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt index 0ebf1642478e..d60326c6c367 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt @@ -19,5 +19,11 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.trustRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope -val Kosmos.trustInteractor by Fixture { TrustInteractor(repository = trustRepository) } +val Kosmos.trustInteractor by Fixture { + TrustInteractor( + applicationScope = applicationCoroutineScope, + repository = trustRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt new file mode 100644 index 000000000000..6eb8a4925082 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.binder + +import android.content.applicationContext +import android.view.layoutInflater +import android.view.mockedLayoutInflater +import android.view.windowManager +import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor +import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel +import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerViewModel +import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerWindowViewModel +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.gesture.TapGestureDetector +import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +val Kosmos.alternateBouncerViewBinder by + Kosmos.Fixture { + AlternateBouncerViewBinder( + applicationScope = applicationCoroutineScope, + alternateBouncerWindowViewModel = { alternateBouncerWindowViewModel }, + alternateBouncerDependencies = { alternateBouncerDependencies }, + windowManager = { windowManager }, + layoutInflater = { mockedLayoutInflater }, + ) + } + +private val Kosmos.alternateBouncerDependencies by + Kosmos.Fixture { + AlternateBouncerDependencies( + viewModel = mock<AlternateBouncerViewModel>(), + swipeUpAnywhereGestureHandler = mock<SwipeUpAnywhereGestureHandler>(), + tapGestureDetector = mock<TapGestureDetector>(), + udfpsIconViewModel = alternateBouncerUdfpsIconViewModel, + udfpsAccessibilityOverlayViewModel = { + mock<AlternateBouncerUdfpsAccessibilityOverlayViewModel>() + }, + messageAreaViewModel = mock<AlternateBouncerMessageAreaViewModel>(), + powerInteractor = powerInteractor, + ) + } + +private val Kosmos.alternateBouncerUdfpsIconViewModel by + Kosmos.Fixture { + AlternateBouncerUdfpsIconViewModel( + context = applicationContext, + configurationInteractor = configurationInteractor, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, + deviceEntryBackgroundViewModel = mock<DeviceEntryBackgroundViewModel>(), + fingerprintPropertyInteractor = fingerprintPropertyInteractor, + udfpsOverlayInteractor = udfpsOverlayInteractor, + alternateBouncerViewModel = alternateBouncerViewModel, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt index 6f168d47038d..6cf668c50fa4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt @@ -33,6 +33,7 @@ var Kosmos.aodBurnInViewModel by Fixture { keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, goneToAodTransitionViewModel = goneToAodTransitionViewModel, + lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, keyguardClockViewModel = keyguardClockViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt index 19e4241c62fa..8549a30c346e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt @@ -22,11 +22,13 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsIntera import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.power.domain.interactor.powerInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.goneToAodTransitionViewModel by Fixture { GoneToAodTransitionViewModel( deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, + powerInteractor = powerInteractor, animationFlow = keyguardTransitionAnimationFlow, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt index 07b4cd4f3251..f45e33bf6865 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt @@ -22,11 +22,13 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsIntera import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.power.domain.interactor.powerInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi -val Kosmos.lockscreenToAodTransitionViewModel by Fixture { +var Kosmos.lockscreenToAodTransitionViewModel by Fixture { LockscreenToAodTransitionViewModel( deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, + powerInteractor = powerInteractor, shadeDependentFlows = shadeDependentFlows, animationFlow = keyguardTransitionAnimationFlow, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt index 690bde7f5b47..7a04aa288dce 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.media.controls.data.repository import android.content.applicationContext import com.android.systemui.kosmos.Kosmos +import com.android.systemui.media.controls.util.mediaSmartspaceLogger import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.util.time.systemClock @@ -26,6 +27,7 @@ val Kosmos.mediaFilterRepository by MediaFilterRepository( applicationContext = applicationContext, systemClock = systemClock, - configurationController = configurationController + configurationController = configurationController, + smartspaceLogger = mediaSmartspaceLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaSmartspaceLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaSmartspaceLoggerKosmos.kt new file mode 100644 index 000000000000..c63dec523228 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaSmartspaceLoggerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.media.controls.util + +import com.android.systemui.kosmos.Kosmos +import org.mockito.Mockito.mock + +var Kosmos.mediaSmartspaceLogger by Kosmos.Fixture { MediaSmartspaceLogger() } +val Kosmos.mockMediaSmartspaceLogger by Kosmos.Fixture { mock(MediaSmartspaceLogger::class.java) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/data/repository/NavigationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/data/repository/NavigationRepositoryKosmos.kt new file mode 100644 index 000000000000..717a30047c95 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/data/repository/NavigationRepositoryKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.navigation.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.navigationbar.navigationModeController + +val Kosmos.navigationRepository by Fixture { + NavigationRepository( + controller = navigationModeController, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorKosmos.kt new file mode 100644 index 000000000000..80fe3b98edff --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigation/domain/interactor/NavigationInteractorKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.navigation.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.navigation.data.repository.navigationRepository + +val Kosmos.navigationInteractor by Fixture { + NavigationInteractor( + repository = navigationRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationModeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationModeControllerKosmos.kt new file mode 100644 index 000000000000..f2fb1a89d7aa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationModeControllerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.navigationbar + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.util.mockito.mock + +val Kosmos.navigationModeController by Fixture { mock<NavigationModeController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepositoryKosmos.kt index d8af3fa98d7b..2f5daaa35286 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepositoryKosmos.kt @@ -18,4 +18,4 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.kosmos.Kosmos -val Kosmos.infiniteGridSizeRepository by Kosmos.Fixture { InfiniteGridSizeRepository() } +val Kosmos.fixedColumnsRepository by Kosmos.Fixture { FixedColumnsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryKosmos.kt new file mode 100644 index 000000000000..696c4bf7bbaf --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase + +val Kosmos.paginatedGridRepository by + Kosmos.Fixture { + testCase.context.orCreateTestableResources + PaginatedGridRepository(testCase.context.resources, configurationRepository) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractorKosmos.kt index 6e1197737630..f4d281db627b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractorKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.qs.panels.data.repository.infiniteGridSizeRepository +import com.android.systemui.qs.panels.data.repository.fixedColumnsRepository -val Kosmos.infiniteGridSizeInteractor by - Kosmos.Fixture { InfiniteGridSizeInteractor(infiniteGridSizeRepository) } +val Kosmos.fixedColumnsSizeInteractor by + Kosmos.Fixture { FixedColumnsSizeInteractor(fixedColumnsRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt index 7f387d7c706d..320c2ec1bb99 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt @@ -20,5 +20,5 @@ import com.android.systemui.kosmos.Kosmos val Kosmos.infiniteGridConsistencyInteractor by Kosmos.Fixture { - InfiniteGridConsistencyInteractor(iconTilesInteractor, infiniteGridSizeInteractor) + InfiniteGridConsistencyInteractor(iconTilesInteractor, fixedColumnsSizeInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt index 82cfaf50f823..be00152cb511 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt @@ -18,8 +18,8 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel -import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridSizeViewModel val Kosmos.infiniteGridLayout by - Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, infiniteGridSizeViewModel) } + Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractorKosmos.kt new file mode 100644 index 000000000000..a922e5df2853 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.data.repository.paginatedGridRepository + +val Kosmos.paginatedGridInteractor by + Kosmos.Fixture { PaginatedGridInteractor(paginatedGridRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModelKosmos.kt index f6dfb8bcd47b..feadc9133bbf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModelKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.panels.ui.viewmodel import com.android.systemui.kosmos.Kosmos -import com.android.systemui.qs.panels.domain.interactor.infiniteGridSizeInteractor +import com.android.systemui.qs.panels.domain.interactor.fixedColumnsSizeInteractor -val Kosmos.infiniteGridSizeViewModel by - Kosmos.Fixture { InfiniteGridSizeViewModelImpl(infiniteGridSizeInteractor) } +val Kosmos.fixedColumnsSizeViewModel by + Kosmos.Fixture { FixedColumnsSizeViewModelImpl(fixedColumnsSizeInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MockTileViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MockTileViewModel.kt new file mode 100644 index 000000000000..5386ecef37aa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MockTileViewModel.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +fun MockTileViewModel( + spec: TileSpec, + state: StateFlow<QSTile.State> = MutableStateFlow(QSTile.State()) +): TileViewModel = mock { + whenever(this.spec).thenReturn(spec) + whenever(this.state).thenReturn(state) + whenever(this.currentState).thenReturn(state.value) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt new file mode 100644 index 000000000000..85e92651cba2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor + +val Kosmos.paginatedGridViewModel by + Kosmos.Fixture { + PaginatedGridViewModel( + iconTilesViewModel, + fixedColumnsSizeViewModel, + iconLabelVisibilityViewModel, + paginatedGridInteractor, + applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt index b07cc7d8612d..fde174d7241e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt @@ -22,7 +22,7 @@ val Kosmos.partitionedGridViewModel by Kosmos.Fixture { PartitionedGridViewModel( iconTilesViewModel, - infiniteGridSizeViewModel, + fixedColumnsSizeViewModel, iconLabelVisibilityViewModel, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt index 066736c1e036..0921eb9e83d3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.scene.domain.interactor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.scene.data.repository.sceneContainerRepository @@ -31,5 +32,6 @@ val Kosmos.sceneInteractor by logger = sceneLogger, sceneFamilyResolvers = { sceneFamilyResolvers }, deviceUnlockedInteractor = deviceUnlockedInteractor, + keyguardEnabledInteractor = keyguardEnabledInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableKosmos.kt new file mode 100644 index 000000000000..f9111bfb3dcf --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableKosmos.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.scene.domain.startable + +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.keyguard.domain.interactor.trustInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.user.domain.interactor.selectedUserInteractor + +val Kosmos.keyguardStateCallbackStartable by Fixture { + KeyguardStateCallbackStartable( + applicationScope = applicationCoroutineScope, + backgroundDispatcher = testDispatcher, + sceneInteractor = sceneInteractor, + selectedUserInteractor = selectedUserInteractor, + deviceEntryInteractor = deviceEntryInteractor, + simBouncerInteractor = simBouncerInteractor, + trustInteractor = trustInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index cf18c0e295ea..8b887d32f3c1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.scene.domain.interactor +package com.android.systemui.scene.domain.startable import com.android.internal.logging.uiEventLogger import com.android.systemui.authentication.domain.interactor.authenticationInteractor @@ -32,7 +32,9 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope import com.android.systemui.model.sysUiState import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.scene.domain.startable.SceneContainerStartable +import com.android.systemui.scene.domain.interactor.sceneBackInteractor +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.session.shared.shadeSessionStorage import com.android.systemui.scene.shared.logger.sceneLogger import com.android.systemui.settings.displayTracker diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt new file mode 100644 index 000000000000..ee69c30fe6b9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import android.content.applicationContext +import com.android.internal.statusbar.statusBarService +import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.deviceconfig.domain.interactor.deviceConfigInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.navigation.domain.interactor.navigationInteractor +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.user.domain.interactor.selectedUserInteractor +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.statusBarStartable by Fixture { + StatusBarStartable( + applicationScope = applicationCoroutineScope, + backgroundDispatcher = testDispatcher, + applicationContext = applicationContext, + selectedUserInteractor = selectedUserInteractor, + sceneInteractor = sceneInteractor, + deviceEntryInteractor = deviceEntryInteractor, + sceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor, + deviceConfigInteractor = deviceConfigInteractor, + navigationInteractor = navigationInteractor, + authenticationInteractor = authenticationInteractor, + powerInteractor = powerInteractor, + deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor, + statusBarService = statusBarService, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index 728c67af5e06..4813794a85bb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -64,6 +64,8 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single) override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow() + override val isDualShadeAlignedToBottom = false + @Deprecated("Use ShadeInteractor instead") override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) { _legacyIsQsExpanded.value = legacyIsQsExpanded diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt index 543d5b6f8857..a00d2f4fc517 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt @@ -71,5 +71,6 @@ val Kosmos.shadeInteractorImpl by userSetupRepository = userSetupRepository, userSwitcherInteractor = userSwitcherInteractor, baseShadeInteractor = baseShadeInteractor, + shadeRepository = shadeRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt index fec102857619..8d4d54749086 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt @@ -19,11 +19,13 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor val Kosmos.overlayShadeViewModel: OverlayShadeViewModel by Kosmos.Fixture { OverlayShadeViewModel( applicationScope = applicationCoroutineScope, sceneInteractor = sceneInteractor, + shadeInteractor = shadeInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt deleted file mode 100644 index a61f7ececc69..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.data - -import com.android.settingslib.statusbar.notification.data.repository.FakeNotificationsSoundPolicyRepository -import com.android.systemui.kosmos.Kosmos - -val Kosmos.notificationsSoundPolicyRepository by - Kosmos.Fixture { FakeNotificationsSoundPolicyRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt index 0614309a3910..e6ca458fabbe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt @@ -16,12 +16,9 @@ package com.android.systemui.statusbar.notification.domain.interactor -import com.android.settingslib.statusbar.notification.data.repository.FakeNotificationsSoundPolicyRepository import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor import com.android.systemui.kosmos.Kosmos - -var Kosmos.notificationsSoundPolicyRepository by - Kosmos.Fixture { FakeNotificationsSoundPolicyRepository() } +import com.android.systemui.statusbar.policy.data.repository.zenModeRepository val Kosmos.notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor by - Kosmos.Fixture { NotificationsSoundPolicyInteractor(notificationsSoundPolicyRepository) } + Kosmos.Fixture { NotificationsSoundPolicyInteractor(zenModeRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt new file mode 100644 index 000000000000..16dc50f96676 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.app.ActivityManager +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.content.pm.LauncherApps +import android.os.UserHandle +import android.provider.DeviceConfig +import androidx.core.os.bundleOf +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.internal.logging.MetricsLogger +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.TestableDependency +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.media.dialog.MediaOutputDialogManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shared.system.ActivityManagerWrapper +import com.android.systemui.shared.system.DevicePolicyManagerWrapper +import com.android.systemui.shared.system.PackageManagerWrapper +import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.RankingBuilder +import com.android.systemui.statusbar.SmartReplyController +import com.android.systemui.statusbar.notification.ColorUpdateLogger +import com.android.systemui.statusbar.notification.ConversationNotificationProcessor +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider +import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager +import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl +import com.android.systemui.statusbar.notification.icon.IconBuilder +import com.android.systemui.statusbar.notification.icon.IconManager +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag +import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor +import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.SmartActionInflaterImpl +import com.android.systemui.statusbar.policy.SmartReplyConstants +import com.android.systemui.statusbar.policy.SmartReplyInflaterImpl +import com.android.systemui.statusbar.policy.SmartReplyStateInflaterImpl +import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent +import com.android.systemui.util.Assert.runWithCurrentThreadAsMainThread +import com.android.systemui.util.DeviceConfigProxyFake +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.wmshell.BubblesManager +import com.google.common.util.concurrent.MoreExecutors +import com.google.common.util.concurrent.SettableFuture +import java.util.Optional +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.test.TestScope +import org.junit.Assert.assertTrue +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mockito + +class ExpandableNotificationRowBuilder( + private val context: Context, + dependency: TestableDependency, + private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic() +) { + + private val mMockLogger: ExpandableNotificationRowLogger + private val mStatusBarStateController: StatusBarStateController + private val mKeyguardBypassController: KeyguardBypassController + private val mGroupMembershipManager: GroupMembershipManager + private val mGroupExpansionManager: GroupExpansionManager + private val mHeadsUpManager: HeadsUpManager + private val mIconManager: IconManager + private val mContentBinder: NotificationRowContentBinder + private val mBindStage: RowContentBindStage + private val mBindPipeline: NotifBindPipeline + private val mBindPipelineEntryListener: NotifCollectionListener + private val mPeopleNotificationIdentifier: PeopleNotificationIdentifier + private val mOnUserInteractionCallback: OnUserInteractionCallback + private val mDismissibilityProvider: NotificationDismissibilityProvider + private val mSmartReplyController: SmartReplyController + private val mSmartReplyConstants: SmartReplyConstants + private val mTestScope: TestScope = TestScope() + private val mBgCoroutineContext = mTestScope.backgroundScope.coroutineContext + private val mMainCoroutineContext = mTestScope.coroutineContext + private val mFakeSystemClock = FakeSystemClock() + private val mMainExecutor = FakeExecutor(mFakeSystemClock) + + init { + featureFlags.setDefault(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE) + featureFlags.setDefault(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING) + + dependency.injectTestDependency(FeatureFlags::class.java, featureFlags) + dependency.injectMockDependency(NotificationMediaManager::class.java) + dependency.injectMockDependency(NotificationShadeWindowController::class.java) + dependency.injectMockDependency(MediaOutputDialogManager::class.java) + + mMockLogger = Mockito.mock(ExpandableNotificationRowLogger::class.java) + mStatusBarStateController = Mockito.mock(StatusBarStateController::class.java) + mKeyguardBypassController = Mockito.mock(KeyguardBypassController::class.java) + mGroupMembershipManager = GroupMembershipManagerImpl() + mSmartReplyController = Mockito.mock(SmartReplyController::class.java) + + val dumpManager = DumpManager() + mGroupExpansionManager = GroupExpansionManagerImpl(dumpManager, mGroupMembershipManager) + mHeadsUpManager = Mockito.mock(HeadsUpManager::class.java) + mIconManager = + IconManager( + Mockito.mock(CommonNotifCollection::class.java), + Mockito.mock(LauncherApps::class.java), + IconBuilder(context), + mTestScope, + mBgCoroutineContext, + mMainCoroutineContext, + ) + + mSmartReplyConstants = + SmartReplyConstants( + /* mainExecutor = */ mMainExecutor, + /* context = */ context, + /* deviceConfig = */ DeviceConfigProxyFake().apply { + setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, + "true", + true + ) + setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.SSIN_ENABLED, + "true", + true + ) + setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, + "false", + true + ) + } + ) + val remoteViewsFactories = getNotifRemoteViewsFactoryContainer(featureFlags) + val remoteInputManager = Mockito.mock(NotificationRemoteInputManager::class.java) + val smartReplyStateInflater = + SmartReplyStateInflaterImpl( + constants = mSmartReplyConstants, + activityManagerWrapper = ActivityManagerWrapper.getInstance(), + packageManagerWrapper = PackageManagerWrapper.getInstance(), + devicePolicyManagerWrapper = DevicePolicyManagerWrapper.getInstance(), + smartRepliesInflater = + SmartReplyInflaterImpl( + constants = mSmartReplyConstants, + keyguardDismissUtil = mock(), + remoteInputManager = remoteInputManager, + smartReplyController = mSmartReplyController, + context = context + ), + smartActionsInflater = + SmartActionInflaterImpl( + constants = mSmartReplyConstants, + activityStarter = mock(), + smartReplyController = mSmartReplyController, + headsUpManager = mHeadsUpManager + ) + ) + val notifLayoutInflaterFactoryProvider = + object : NotifLayoutInflaterFactory.Provider { + override fun provide( + row: ExpandableNotificationRow, + layoutType: Int + ): NotifLayoutInflaterFactory = + NotifLayoutInflaterFactory(row, layoutType, remoteViewsFactories) + } + val conversationProcessor = + ConversationNotificationProcessor( + mock(), + mock(), + ) + mContentBinder = + if (NotificationRowContentBinderRefactor.isEnabled) + NotificationRowContentBinderImpl( + mock(), + remoteInputManager, + conversationProcessor, + mock(), + mock(), + mock(), + smartReplyStateInflater, + notifLayoutInflaterFactoryProvider, + mock(), + mock(), + ) + else + NotificationContentInflater( + mock(), + remoteInputManager, + conversationProcessor, + mock(), + mock(), + smartReplyStateInflater, + notifLayoutInflaterFactoryProvider, + mock(), + mock(), + ) + mContentBinder.setInflateSynchronously(true) + mBindStage = + RowContentBindStage( + mContentBinder, + mock(), + mock(), + ) + + val collection = Mockito.mock(CommonNotifCollection::class.java) + + mBindPipeline = + NotifBindPipeline( + collection, + Mockito.mock(NotifBindPipelineLogger::class.java), + NotificationEntryProcessorFactoryExecutorImpl(mMainExecutor), + ) + mBindPipeline.setStage(mBindStage) + + val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java) + Mockito.verify(collection).addCollectionListener(collectionListenerCaptor.capture()) + mBindPipelineEntryListener = collectionListenerCaptor.value + mPeopleNotificationIdentifier = Mockito.mock(PeopleNotificationIdentifier::class.java) + mOnUserInteractionCallback = Mockito.mock(OnUserInteractionCallback::class.java) + mDismissibilityProvider = Mockito.mock(NotificationDismissibilityProvider::class.java) + val mFutureDismissalRunnable = Mockito.mock(Runnable::class.java) + whenever( + mOnUserInteractionCallback.registerFutureDismissal( + ArgumentMatchers.any(), + ArgumentMatchers.anyInt() + ) + ) + .thenReturn(mFutureDismissalRunnable) + } + + private fun getNotifRemoteViewsFactoryContainer( + featureFlags: FeatureFlags, + ): NotifRemoteViewsFactoryContainer { + return NotifRemoteViewsFactoryContainerImpl( + featureFlags, + PrecomputedTextViewFactory(), + BigPictureLayoutInflaterFactory(), + NotificationOptimizedLinearLayoutFactory(), + { Mockito.mock(NotificationViewFlipperFactory::class.java) }, + ) + } + + fun createRow(notification: Notification): ExpandableNotificationRow { + val channel = + NotificationChannel( + notification.channelId, + notification.channelId, + NotificationManager.IMPORTANCE_DEFAULT + ) + channel.isBlockable = true + val entry = + NotificationEntryBuilder() + .setPkg(PKG) + .setOpPkg(PKG) + .setId(123321) + .setUid(UID) + .setInitialPid(2000) + .setNotification(notification) + .setUser(USER_HANDLE) + .setPostTime(System.currentTimeMillis()) + .setChannel(channel) + .build() + + // it is for mitigating Rank building process. + if (notification.isConversationStyleNotification) { + val rb = RankingBuilder(entry.ranking) + rb.setIsConversation(true) + entry.ranking = rb.build() + } + + return generateRow(entry, FLAG_CONTENT_VIEW_ALL) + } + + private fun generateRow( + entry: NotificationEntry, + @InflationFlag extraInflationFlags: Int + ): ExpandableNotificationRow { + // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be + // set, but we do not want to override an existing value that is needed by a specific test. + + val rowFuture: SettableFuture<ExpandableNotificationRow> = SettableFuture.create() + val rowInflaterTask = + RowInflaterTask(mFakeSystemClock, Mockito.mock(RowInflaterTaskLogger::class.java)) + rowInflaterTask.inflate(context, null, entry, MoreExecutors.directExecutor()) { inflatedRow + -> + rowFuture.set(inflatedRow) + } + val row = rowFuture.get(1, TimeUnit.SECONDS) + + entry.row = row + mIconManager.createIcons(entry) + mBindPipelineEntryListener.onEntryInit(entry) + mBindPipeline.manageRow(entry, row) + row.initialize( + entry, + Mockito.mock(RemoteInputViewSubcomponent.Factory::class.java), + APP_NAME, + entry.key, + mMockLogger, + mKeyguardBypassController, + mGroupMembershipManager, + mGroupExpansionManager, + mHeadsUpManager, + mBindStage, + Mockito.mock(OnExpandClickListener::class.java), + Mockito.mock(CoordinateOnClickListener::class.java), + FalsingManagerFake(), + mStatusBarStateController, + mPeopleNotificationIdentifier, + mOnUserInteractionCallback, + Optional.of(Mockito.mock(BubblesManager::class.java)), + Mockito.mock(NotificationGutsManager::class.java), + mDismissibilityProvider, + Mockito.mock(MetricsLogger::class.java), + Mockito.mock(NotificationChildrenContainerLogger::class.java), + Mockito.mock(ColorUpdateLogger::class.java), + mSmartReplyConstants, + mSmartReplyController, + featureFlags, + Mockito.mock(IStatusBarService::class.java) + ) + row.setAboveShelfChangedListener { aboveShelf: Boolean -> } + mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags) + inflateAndWait(entry) + return row + } + + private fun inflateAndWait(entry: NotificationEntry) { + val countDownLatch = CountDownLatch(1) + mBindStage.requestRebind(entry) { en: NotificationEntry? -> countDownLatch.countDown() } + runWithCurrentThreadAsMainThread(mMainExecutor::runAllReady) + assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)) + } + + companion object { + private const val APP_NAME = "appName" + private const val PKG = "com.android.systemui" + private const val UID = 1000 + private val USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser()) + + private const val IS_CONVERSATION_FLAG = "test.isConversation" + + private val Notification.isConversationStyleNotification + get() = extras.getBoolean(IS_CONVERSATION_FLAG, false) + + fun markAsConversation(builder: Notification.Builder) { + builder.addExtras(bundleOf(IS_CONVERSATION_FLAG to true)) + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt new file mode 100644 index 000000000000..84ef4b5c21c3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/data/repository/NotificationRowRepositoryKosmos.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel +import kotlinx.coroutines.flow.MutableStateFlow + +val Kosmos.fakeNotificationRowRepository by Fixture { FakeNotificationRowRepository() } + +class FakeNotificationRowRepository : NotificationRowRepository { + override val richOngoingContentModel = MutableStateFlow<RichOngoingContentModel?>(null) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt new file mode 100644 index 000000000000..3a7d7ba064d0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository + +fun Kosmos.getNotificationRowInteractor(repository: NotificationRowRepository) = + NotificationRowInteractor(repository = repository) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt new file mode 100644 index 000000000000..00f45b220654 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/TimerViewModelKosmos.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.ui.viewmodel + +import com.android.systemui.dump.dumpManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository +import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor + +fun Kosmos.getTimerViewModel(repository: NotificationRowRepository) = + TimerViewModel( + dumpManager = dumpManager, + rowInteractor = getNotificationRowInteractor(repository), + ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 299486fc8ef4..ffd8aabdd964 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor @@ -86,6 +87,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { primaryBouncerToLockscreenTransitionViewModel = primaryBouncerToLockscreenTransitionViewModel, aodBurnInViewModel = aodBurnInViewModel, + communalSceneInteractor = communalSceneInteractor, unfoldTransitionInteractor = unfoldTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt index 1851c89ecd94..6574946726a9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt @@ -36,3 +36,14 @@ val Kosmos.systemUIDialogFactory: SystemUIDialogFactory by val Kosmos.mockSystemUIDialogFactory: SystemUIDialog.Factory by Kosmos.Fixture { mock<SystemUIDialog.Factory>() } + +val Kosmos.systemUIDialogDotFactory by + Kosmos.Fixture { + SystemUIDialog.Factory( + applicationContext, + systemUIDialogManager, + sysUiState, + broadcastDispatcher, + dialogTransitionAnimator, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt index 16dab40d6edc..5aece1bbbd31 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt @@ -16,14 +16,7 @@ package com.android.systemui.statusbar.policy.data import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepositoryModule -import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepositoryModule import dagger.Module -@Module( - includes = - [ - FakeDeviceProvisioningRepositoryModule::class, - FakeZenModeRepositoryModule::class, - ] -) +@Module(includes = [FakeDeviceProvisioningRepositoryModule::class]) object FakeStatusBarPolicyDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt deleted file mode 100644 index c4d78678e59a..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.policy.data.repository - -import android.app.NotificationManager.Policy -import android.provider.Settings -import com.android.systemui.dagger.SysUISingleton -import dagger.Binds -import dagger.Module -import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow - -@SysUISingleton -class FakeZenModeRepository @Inject constructor() : ZenModeRepository { - override val zenMode: MutableStateFlow<Int> = MutableStateFlow(Settings.Global.ZEN_MODE_OFF) - override val consolidatedNotificationPolicy: MutableStateFlow<Policy?> = - MutableStateFlow( - Policy( - /* priorityCategories = */ 0, - /* priorityCallSenders = */ 0, - /* priorityMessageSenders = */ 0, - ) - ) - - fun setSuppressedVisualEffects(suppressedVisualEffects: Int) { - consolidatedNotificationPolicy.value = - Policy( - /* priorityCategories = */ 0, - /* priorityCallSenders = */ 0, - /* priorityMessageSenders = */ 0, - /* suppressedVisualEffects = */ suppressedVisualEffects, - ) - } -} - -@Module -interface FakeZenModeRepositoryModule { - @Binds fun bindFake(fake: FakeZenModeRepository): ZenModeRepository -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt index 1ec751193747..c7fda54af39d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy.data.repository +import com.android.settingslib.statusbar.notification.data.repository.FakeZenModeRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 1f2ecb7d172d..ed335f9a1834 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -39,7 +39,7 @@ class FakeUserRepository @Inject constructor() : UserRepository { // User id to represent a non system (human) user id. We presume this is the main user. const val MAIN_USER_ID = 10 - private const val DEFAULT_SELECTED_USER = 0 + const val DEFAULT_SELECTED_USER = 0 private val DEFAULT_SELECTED_USER_INFO = UserInfo( /* id= */ DEFAULT_SELECTED_USER, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyKosmos.kt new file mode 100644 index 000000000000..cf902ef8906d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.fakeDeviceConfigProxy by Fixture { DeviceConfigProxyFake() } + +val Kosmos.deviceConfigProxy by Fixture<DeviceConfigProxy> { fakeDeviceConfigProxy } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java index 84ace7cc9540..5fae38f4a82b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java @@ -18,6 +18,7 @@ import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.Callback; +import com.android.systemui.statusbar.policy.CastDevice; import java.util.ArrayList; import java.util.List; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt index 21d59f09711c..fcea9e7b2a3f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt @@ -42,6 +42,7 @@ class FakeAudioRepository : AudioRepository { private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf() private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf() + private val deviceCategories: MutableMap<String, Int> = mutableMapOf() private fun getAudioStreamModelState( audioStream: AudioStream @@ -103,4 +104,12 @@ class FakeAudioRepository : AudioRepository { override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) { mutableRingerMode.value = mode } + + fun setBluetoothAudioDeviceCategory(bluetoothAddress: String, category: Int) { + deviceCategories[bluetoothAddress] = category + } + + override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int { + return deviceCategories[bluetoothAddress] ?: AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt index 141f2426f365..83adc799b471 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt @@ -18,9 +18,11 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothProfile import android.graphics.drawable.Drawable import android.graphics.drawable.TestStubDrawable import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LeAudioProfile import com.android.settingslib.media.BluetoothMediaDevice import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice @@ -59,11 +61,17 @@ object TestMediaDevicesFactory { whenever(name).thenReturn(deviceName) whenever(address).thenReturn(deviceAddress) } + val leAudioProfile = + mock<LeAudioProfile> { + whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO) + whenever(isEnabled(bluetoothDevice)).thenReturn(true) + } val cachedBluetoothDevice: CachedBluetoothDevice = mock { whenever(isHearingAidDevice).thenReturn(true) whenever(address).thenReturn(deviceAddress) whenever(device).thenReturn(bluetoothDevice) whenever(name).thenReturn(deviceName) + whenever(profiles).thenReturn(listOf(leAudioProfile)) } return mock<BluetoothMediaDevice> { whenever(name).thenReturn(deviceName) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt index 95a7b9bb185b..6a46d56ba55a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt @@ -17,8 +17,10 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.kosmos.testScope import com.android.systemui.media.spatializerInteractor +import com.android.systemui.volume.data.repository.audioRepository import com.android.systemui.volume.domain.interactor.audioOutputInteractor val Kosmos.spatialAudioComponentInteractor by @@ -26,6 +28,8 @@ val Kosmos.spatialAudioComponentInteractor by SpatialAudioComponentInteractor( audioOutputInteractor, spatializerInteractor, + audioRepository, + backgroundCoroutineContext, testScope.backgroundScope, ) } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 7f542d130cf7..994bdb58f543 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -310,6 +310,14 @@ message SystemMessage { // Package: android NOTE_USB_UVC = 75; + // Inform the user about adaptive notifications + // Package: com.android.systemui + NOTE_ADAPTIVE_NOTIFICATIONS = 76; + + // Warn the user that the device's Headless System User Mode status doesn't match the build's. + // Package: android + NOTE_WRONG_HSUM_STATUS = 77; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 48bc803f4a5c..ad216b59c93d 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -276,3 +276,77 @@ sh_test_host { ":services.core.ravenwood.keep_all", ], } + +java_library { + name: "services.fakes.ravenwood-jarjar", + installable: false, + srcs: [":services.fakes-sources"], + libs: [ + "ravenwood-framework", + "services.core.ravenwood", + ], + jarjar_rules: ":ravenwood-services-jarjar-rules", + visibility: ["//visibility:private"], +} + +java_library { + name: "mockito-ravenwood-prebuilt", + installable: false, + static_libs: [ + "mockito-robolectric-prebuilt", + ], +} + +java_library { + name: "inline-mockito-ravenwood-prebuilt", + installable: false, + static_libs: [ + "inline-mockito-robolectric-prebuilt", + ], +} + +android_ravenwood_libgroup { + name: "ravenwood-runtime", + libs: [ + "100-framework-minus-apex.ravenwood", + "200-kxml2-android", + + "ravenwood-runtime-common-ravenwood", + + "android.test.mock.ravenwood", + "ravenwood-helper-runtime", + "hoststubgen-helper-runtime.ravenwood", + "services.core.ravenwood-jarjar", + "services.fakes.ravenwood-jarjar", + + // Provide runtime versions of utils linked in below + "junit", + "truth", + "flag-junit", + "ravenwood-framework", + "ravenwood-junit-impl", + "ravenwood-junit-impl-flag", + "mockito-ravenwood-prebuilt", + "inline-mockito-ravenwood-prebuilt", + + // It's a stub, so it should be towards the end. + "z00-all-updatable-modules-system-stubs", + ], + jni_libs: [ + "libandroid_runtime", + "libravenwood_runtime", + ], +} + +android_ravenwood_libgroup { + name: "ravenwood-utils", + libs: [ + "junit", + "truth", + "flag-junit", + "ravenwood-framework", + "ravenwood-junit", + "mockito-ravenwood-prebuilt", + "inline-mockito-ravenwood-prebuilt", + ], +} diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 93531508b3eb..b4efae3a05e4 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -11,6 +11,16 @@ flag { } flag { + name: "always_allow_observing_touch_events" + namespace: "accessibility" + description: "Always allows InputFilter observing SOURCE_TOUCHSCREEN events, even if touch exploration is enabled." + bug: "344604959" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "resettable_dynamic_properties" namespace: "accessibility" description: "Maintains initial copies of a11yServiceInfo dynamic properties so they can reset on disconnect." diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 5fb60e75cf85..f9196f3e0e0e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -1087,7 +1087,15 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } } - private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) { + private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) { + if (Flags.alwaysAllowObservingTouchEvents()) { + final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN); + if (isTouchEvent && !canShareGenericTouchEvent()) { + return false; + } + final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; + return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0; + } // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing // touch exploration. if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN) @@ -1095,13 +1103,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo return false; } final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; - return (mCombinedGenericMotionEventSources - & mCombinedMotionEventObservedSources - & eventSourceWithoutClass) - != 0; + return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0; } - private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) { + private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) { + if (Flags.alwaysAllowObservingTouchEvents()) { + final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; + return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0; + } // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing // touch exploration. if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN) @@ -1109,7 +1118,22 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo return false; } final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; - return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0; + return (mCombinedGenericMotionEventSources + & mCombinedMotionEventObservedSources + & eventSourceWithoutClass) + != 0; + } + + private boolean canShareGenericTouchEvent() { + if ((mCombinedMotionEventObservedSources & InputDevice.SOURCE_TOUCHSCREEN) != 0) { + // Share touch events if a MotionEvent-observing service wants them. + return true; + } + if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) == 0) { + // Share touch events if touch exploration is not enabled. + return true; + } + return false; } public void setCombinedGenericMotionEventSources(int sources) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 4f9db8bece63..acd80eeac558 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -47,6 +47,11 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP; import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityShortcutActivated; import static com.android.internal.util.FunctionalUtils.ignoreRemoteException; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -923,25 +928,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub newValue, restoredFromSdk); } } - case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> { - synchronized (mLock) { - restoreAccessibilityButtonTargetsLocked( - previousValue, newValue); - } - } - case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> { - if (!android.view.accessibility.Flags.a11yQsShortcut()) { - return; - } - restoreAccessibilityQsTargets(newValue); - } - case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> { - if (!android.view.accessibility.Flags - .restoreA11yShortcutTargetService()) { - return; - } - restoreAccessibilityShortcutTargetService(previousValue, newValue); - } + case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, + Settings.Secure.ACCESSIBILITY_QS_TARGETS, + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> + restoreShortcutTargets(newValue, + ShortcutUtils.convertToType(which)); } } } @@ -1040,7 +1031,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userState.mUserId, targetsFromSetting, str -> str); - readAccessibilityButtonTargetsLocked(userState); + readAccessibilityShortcutTargetsLocked(userState, SOFTWARE); onUserStateChangedLocked(userState); } @@ -1720,12 +1711,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } // Turn on/off a11y qs shortcut for the a11y features based on the change in QS Panel if (!a11yFeaturesToEnable.isEmpty()) { - enableShortcutForTargets(/* enable= */ true, UserShortcutType.QUICK_SETTINGS, + enableShortcutForTargets(/* enable= */ true, QUICK_SETTINGS, a11yFeaturesToEnable, userId); } if (!a11yFeaturesToRemove.isEmpty()) { - enableShortcutForTargets(/* enable= */ false, UserShortcutType.QUICK_SETTINGS, + enableShortcutForTargets(/* enable= */ false, QUICK_SETTINGS, a11yFeaturesToRemove, userId); } } @@ -2057,100 +2048,78 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** - * User could enable accessibility services and configure accessibility button during the SUW. - * Merges current value of accessibility button settings into the restored one to make sure - * user's preferences of accessibility button updated in SUW are not lost. - * - * Called only during settings restore; currently supports only the owner user - * TODO: http://b/22388012 - */ - void restoreAccessibilityButtonTargetsLocked(String oldSetting, String newSetting) { - final Set<String> targetsFromSetting = new ArraySet<>(); - readColonDelimitedStringToSet(oldSetting, str -> str, targetsFromSetting, - /* doMerge = */false); - readColonDelimitedStringToSet(newSetting, str -> str, targetsFromSetting, - /* doMerge = */true); - - final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); - userState.mAccessibilityButtonTargets.clear(); - userState.mAccessibilityButtonTargets.addAll(targetsFromSetting); - persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, - UserHandle.USER_SYSTEM, userState.mAccessibilityButtonTargets, str -> str); - - scheduleNotifyClientsOfServicesStateChangeLocked(userState); - onUserStateChangedLocked(userState); - } - - /** * User could configure accessibility shortcut during the SUW before restoring user data. * Merges the current value and the new value to make sure we don't lost the setting the user's - * preferences of accessibility qs shortcut updated in SUW are not lost. - * - * Called only during settings restore; currently supports only the owner user + * preferences of accessibility shortcut updated in SUW are not lost. + * Called only during settings restore; currently supports only the owner user. + * <P> + * Throws an exception if used with {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP}. + * </P> * TODO: http://b/22388012 */ - private void restoreAccessibilityQsTargets(String newValue) { + private void restoreShortcutTargets(String newValue, + @UserShortcutType int shortcutType) { + assertNoTapShortcut(shortcutType); + if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) { + return; + } + if (shortcutType == HARDWARE + && !android.view.accessibility.Flags.restoreA11yShortcutTargetService()) { + return; + } + synchronized (mLock) { final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); - final Set<String> mergedTargets = userState.getA11yQsTargets(); - readColonDelimitedStringToSet(newValue, str -> str, mergedTargets, - /* doMerge = */ true); + final Set<String> mergedTargets = (shortcutType == HARDWARE) + ? new ArraySet<>(ShortcutUtils.getShortcutTargetsFromSettings( + mContext, shortcutType, userState.mUserId)) + : userState.getShortcutTargetsLocked(shortcutType); + + // If dealing with the hardware shortcut, + // remove the default service if it wasn't present before restore, + // but only if the raw shortcut setting is not null (edge case during SUW). + // Otherwise, merge the old and new targets normally. + if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore() + && shortcutType == HARDWARE) { + final String defaultService = + mContext.getString(R.string.config_defaultAccessibilityService); + final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService) + ? null : ComponentName.unflattenFromString(defaultService); + boolean shouldClearDefaultService = defaultServiceComponent != null + && !stringSetContainsComponentName(mergedTargets, defaultServiceComponent); + readColonDelimitedStringToSet(newValue, str -> str, + mergedTargets, /*doMerge=*/true); + + if (shouldClearDefaultService && stringSetContainsComponentName( + mergedTargets, defaultServiceComponent)) { + Slog.i(LOG_TAG, "Removing default service " + defaultService + + " from restore of " + + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); + mergedTargets.removeIf(str -> + defaultServiceComponent.equals(ComponentName.unflattenFromString(str))); + } + if (mergedTargets.isEmpty()) { + return; + } + } else { + readColonDelimitedStringToSet(newValue, str -> str, mergedTargets, + /* doMerge = */ true); + } - userState.updateA11yQsTargetLocked(mergedTargets); - persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_QS_TARGETS, + userState.updateShortcutTargetsLocked(mergedTargets, shortcutType); + persistColonDelimitedSetToSettingLocked(ShortcutUtils.convertToKey(shortcutType), UserHandle.USER_SYSTEM, mergedTargets, str -> str); scheduleNotifyClientsOfServicesStateChangeLocked(userState); onUserStateChangedLocked(userState); } } - /** - * Merges the old and restored value of - * {@link Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE}. - * - * <p>Also clears out {@link R.string#config_defaultAccessibilityService} from - * the merged set if it was not present before restoring. - */ - private void restoreAccessibilityShortcutTargetService( - String oldValue, String restoredValue) { - final Set<String> targetsFromSetting = new ArraySet<>(); - readColonDelimitedStringToSet(oldValue, str -> str, - targetsFromSetting, /*doMerge=*/false); - final String defaultService = - mContext.getString(R.string.config_defaultAccessibilityService); - final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService) - ? null : ComponentName.unflattenFromString(defaultService); - boolean shouldClearDefaultService = defaultServiceComponent != null - && !stringSetContainsComponentName(targetsFromSetting, defaultServiceComponent); - readColonDelimitedStringToSet(restoredValue, str -> str, - targetsFromSetting, /*doMerge=*/true); - if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore()) { - if (shouldClearDefaultService && stringSetContainsComponentName( - targetsFromSetting, defaultServiceComponent)) { - Slog.i(LOG_TAG, "Removing default service " + defaultService - + " from restore of " - + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); - targetsFromSetting.removeIf(str -> - defaultServiceComponent.equals(ComponentName.unflattenFromString(str))); - } - if (targetsFromSetting.isEmpty()) { - return; - } - } - synchronized (mLock) { - final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); - final Set<String> shortcutTargets = - userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE); - shortcutTargets.clear(); - shortcutTargets.addAll(targetsFromSetting); - persistColonDelimitedSetToSettingLocked( - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, - UserHandle.USER_SYSTEM, targetsFromSetting, str -> str); - scheduleNotifyClientsOfServicesStateChangeLocked(userState); - onUserStateChangedLocked(userState); - } + private String getRawShortcutSetting(int userId, @UserShortcutType int shortcutType) { + return Settings.Secure.getStringForUser(mContext.getContentResolver(), + ShortcutUtils.convertToKey(shortcutType), userId); } + /** * Returns {@code true} if the set contains the provided non-null {@link ComponentName}. * @@ -2263,7 +2232,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void showAccessibilityTargetsSelection(int displayId, @UserShortcutType int shortcutType) { final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); - final String chooserClassName = (shortcutType == UserShortcutType.HARDWARE) + final String chooserClassName = (shortcutType == HARDWARE) ? AccessibilityShortcutChooserActivity.class.getName() : AccessibilityButtonChooserActivity.class.getName(); intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName); @@ -3236,9 +3205,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub somethingChanged |= readAudioDescriptionEnabledSettingLocked(userState); somethingChanged |= readMagnificationEnabledSettingsLocked(userState); somethingChanged |= readAutoclickEnabledSettingLocked(userState); - somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState); - somethingChanged |= readAccessibilityQsTargetsLocked(userState); - somethingChanged |= readAccessibilityButtonTargetsLocked(userState); + somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, HARDWARE); + somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS); + somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, SOFTWARE); somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState); somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState); somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState); @@ -3386,60 +3355,34 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.setSendMotionEventsEnabled(sendMotionEvents); } - private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) { - final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userState.mUserId); + /** + * Throws an exception for {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP} types. + */ + private boolean readAccessibilityShortcutTargetsLocked(AccessibilityUserState userState, + @UserShortcutType int shortcutType) { + assertNoTapShortcut(shortcutType); + final String settingValue = getRawShortcutSetting(userState.mUserId, shortcutType); final Set<String> targetsFromSetting = new ArraySet<>(); - readColonDelimitedStringToSet(settingValue, str -> str, targetsFromSetting, false); - // Fall back to device's default a11y service, only when setting is never updated. - if (settingValue == null) { + // If dealing with an empty hardware shortcut, fall back to the default value. + if (shortcutType == HARDWARE && settingValue == null) { final String defaultService = mContext.getString( R.string.config_defaultAccessibilityService); if (!TextUtils.isEmpty(defaultService)) { - targetsFromSetting.add(defaultService); + // Convert to component name to reformat the target if it has a relative path. + ComponentName name = ComponentName.unflattenFromString(defaultService); + if (name != null) { + targetsFromSetting.add(name.flattenToString()); + } } + } else { + readColonDelimitedStringToSet(settingValue, str -> str, targetsFromSetting, false); } - final Set<String> currentTargets = - userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE); - if (targetsFromSetting.equals(currentTargets)) { - return false; - } - currentTargets.clear(); - currentTargets.addAll(targetsFromSetting); - scheduleNotifyClientsOfServicesStateChangeLocked(userState); - return true; - } - - private boolean readAccessibilityQsTargetsLocked(AccessibilityUserState userState) { - final Set<String> targetsFromSetting = new ArraySet<>(); - readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_QS_TARGETS, - userState.mUserId, str -> str, targetsFromSetting); - - final Set<String> currentTargets = - userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS); - if (targetsFromSetting.equals(currentTargets)) { - return false; - } - userState.updateA11yQsTargetLocked(targetsFromSetting); - scheduleNotifyClientsOfServicesStateChangeLocked(userState); - return true; - } - - private boolean readAccessibilityButtonTargetsLocked(AccessibilityUserState userState) { - final Set<String> targetsFromSetting = new ArraySet<>(); - readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, - userState.mUserId, str -> str, targetsFromSetting); - - final Set<String> currentTargets = - userState.getShortcutTargetsLocked(UserShortcutType.SOFTWARE); - if (targetsFromSetting.equals(currentTargets)) { - return false; + if (userState.updateShortcutTargetsLocked(targetsFromSetting, shortcutType)) { + scheduleNotifyClientsOfServicesStateChangeLocked(userState); + return true; } - currentTargets.clear(); - currentTargets.addAll(targetsFromSetting); - scheduleNotifyClientsOfServicesStateChangeLocked(userState); - return true; + return false; } private boolean readAccessibilityButtonTargetComponentLocked(AccessibilityUserState userState) { @@ -3487,14 +3430,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) { final Set<String> currentTargets = - userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE); - final int lastSize = currentTargets.size(); - if (lastSize == 0) { - return; - } + userState.getShortcutTargetsLocked(HARDWARE); currentTargets.removeIf( name -> !userState.isShortcutTargetInstalledLocked(name)); - if (lastSize == currentTargets.size()) { + if (!userState.updateShortcutTargetsLocked(currentTargets, HARDWARE)) { return; } @@ -3680,13 +3619,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final Set<String> currentTargets = userState.getShortcutTargetsLocked(UserShortcutType.SOFTWARE); - final int lastSize = currentTargets.size(); - if (lastSize == 0) { - return; - } currentTargets.removeIf( name -> !userState.isShortcutTargetInstalledLocked(name)); - if (lastSize == currentTargets.size()) { + if (!userState.updateShortcutTargetsLocked(currentTargets, SOFTWARE)) { return; } @@ -3719,8 +3654,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } final Set<String> buttonTargets = - userState.getShortcutTargetsLocked(UserShortcutType.SOFTWARE); - int lastSize = buttonTargets.size(); + userState.getShortcutTargetsLocked(SOFTWARE); buttonTargets.removeIf(name -> { if (packageName != null && name != null && !name.contains(packageName)) { return false; @@ -3752,13 +3686,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } return false; }); - boolean changed = (lastSize != buttonTargets.size()); - lastSize = buttonTargets.size(); final Set<String> shortcutKeyTargets = - userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE); + userState.getShortcutTargetsLocked(HARDWARE); final Set<String> qsShortcutTargets = - userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS); + userState.getShortcutTargetsLocked(QUICK_SETTINGS); userState.mEnabledServices.forEach(componentName -> { if (packageName != null && componentName != null && !packageName.equals(componentName.getPackageName())) { @@ -3790,8 +3722,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub + " should be assign to the button or shortcut."); buttonTargets.add(serviceName); }); - changed |= (lastSize != buttonTargets.size()); - if (!changed) { + if (!userState.updateShortcutTargetsLocked(buttonTargets, SOFTWARE)) { return; } @@ -3815,10 +3746,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } final Set<String> targets = - userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS); + userState.getShortcutTargetsLocked(QUICK_SETTINGS); // Removes the targets that are no longer installed on the device. - boolean somethingChanged = targets.removeIf( + targets.removeIf( name -> !userState.isShortcutTargetInstalledLocked(name)); // Add the target if the a11y service is enabled and the tile exist in QS panel Set<ComponentName> enabledServices = userState.getEnabledServicesLocked(); @@ -3829,14 +3760,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub ComponentName tileService = a11yFeatureToTileService.getOrDefault(enabledService, null); if (tileService != null && currentA11yTilesInQsPanel.contains(tileService)) { - somethingChanged |= targets.add(enabledService.flattenToString()); + targets.add(enabledService.flattenToString()); } } - if (!somethingChanged) { + if (!userState.updateShortcutTargetsLocked(targets, QUICK_SETTINGS)) { return; } - userState.updateA11yQsTargetLocked(targets); // Update setting key with new value. persistColonDelimitedSetToSettingLocked( @@ -3862,14 +3792,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3); shortcutTypeAndShortcutSetting.add( - new Pair<>(UserShortcutType.HARDWARE, + new Pair<>(HARDWARE, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)); shortcutTypeAndShortcutSetting.add( new Pair<>(UserShortcutType.SOFTWARE, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS)); if (android.view.accessibility.Flags.a11yQsShortcut()) { shortcutTypeAndShortcutSetting.add( - new Pair<>(UserShortcutType.QUICK_SETTINGS, + new Pair<>(QUICK_SETTINGS, Settings.Secure.ACCESSIBILITY_QS_TARGETS)); } @@ -3883,7 +3813,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub shortcutSettingName, userState.mUserId, currentTargets, str -> str); - if (shortcutType != UserShortcutType.QUICK_SETTINGS) { + if (shortcutType != QUICK_SETTINGS) { continue; } @@ -3968,7 +3898,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::performAccessibilityShortcutInternal, this, - Display.DEFAULT_DISPLAY, UserShortcutType.HARDWARE, targetName)); + Display.DEFAULT_DISPLAY, HARDWARE, targetName)); } /** @@ -4115,7 +4045,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final boolean requestA11yButton = (installedServiceInfo.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; // Turns on / off the accessibility service - if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == UserShortcutType.HARDWARE) + if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == HARDWARE) || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) { if (serviceConnection == null) { logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType, @@ -4129,7 +4059,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } return true; } - if (shortcutType == UserShortcutType.HARDWARE && targetSdk > Build.VERSION_CODES.Q + if (shortcutType == HARDWARE && targetSdk > Build.VERSION_CODES.Q && requestA11yButton) { if (!userState.getEnabledServicesLocked().contains(assignedTarget)) { enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId); @@ -4222,7 +4152,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub validNewTargets = newTargets; // filter out targets that doesn't have qs shortcut - if (shortcutType == UserShortcutType.QUICK_SETTINGS) { + if (shortcutType == QUICK_SETTINGS) { validNewTargets = newTargets.stream().filter(target -> { ComponentName targetComponent = ComponentName.unflattenFromString(target); return featureToTileMap.containsKey(targetComponent); @@ -4240,10 +4170,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /* defaultEmptyString= */ "" ); - if (shortcutType == UserShortcutType.QUICK_SETTINGS) { + if (shortcutType == QUICK_SETTINGS) { int numOfFeatureChanged = Math.abs(currentTargets.size() - validNewTargets.size()); logMetricForQsShortcutConfiguration(enable, numOfFeatureChanged); - userState.updateA11yQsTargetLocked(validNewTargets); + userState.updateShortcutTargetsLocked(validNewTargets, QUICK_SETTINGS); scheduleNotifyClientsOfServicesStateChangeLocked(userState); onUserStateChangedLocked(userState); } @@ -4257,7 +4187,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } // Add or Remove tile in QS Panel - if (shortcutType == UserShortcutType.QUICK_SETTINGS) { + if (shortcutType == QUICK_SETTINGS) { mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::updateA11yTileServicesInQuickSettingsPanel, this, validNewTargets, currentTargets, userId)); @@ -4266,7 +4196,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!enable) { return; } - if (shortcutType == UserShortcutType.HARDWARE) { + if (shortcutType == HARDWARE) { skipVolumeShortcutDialogTimeoutRestriction(userId); if (com.android.server.accessibility.Flags.enableHardwareShortcutDisablesWarning()) { persistIntToSetting( @@ -4461,6 +4391,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub shortcutTargets.add(serviceName); } } + userState.updateShortcutTargetsLocked(Set.copyOf(shortcutTargets), shortcutType); return shortcutTargets; } } @@ -5672,7 +5603,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub || mShowImeWithHardKeyboardUri.equals(uri)) { userState.reconcileSoftKeyboardModeWithSettingsLocked(); } else if (mAccessibilityShortcutServiceIdUri.equals(uri)) { - if (readAccessibilityShortcutKeySettingLocked(userState)) { + if (readAccessibilityShortcutTargetsLocked(userState, HARDWARE)) { onUserStateChangedLocked(userState); } } else if (mAccessibilityButtonComponentIdUri.equals(uri)) { @@ -5680,7 +5611,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub onUserStateChangedLocked(userState); } } else if (mAccessibilityButtonTargetsUri.equals(uri)) { - if (readAccessibilityButtonTargetsLocked(userState)) { + if (readAccessibilityShortcutTargetsLocked(userState, SOFTWARE)) { onUserStateChangedLocked(userState); } } else if (mUserNonInteractiveUiTimeoutUri.equals(uri) @@ -6505,4 +6436,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub String metricId = enable ? METRIC_ID_QS_SHORTCUT_ADD : METRIC_ID_QS_SHORTCUT_REMOVE; Counter.logIncrementWithUid(metricId, Binder.getCallingUid(), numOfFeatures); } + + private void assertNoTapShortcut(@UserShortcutType int shortcutType) { + if ((shortcutType & (TRIPLETAP | TWOFINGER_DOUBLETAP)) != 0) { + throw new IllegalArgumentException("Tap shortcuts are not supported."); + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index a37a1841fcc0..de1c86a1ac06 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -777,12 +777,15 @@ class AccessibilityUserState { * @return The array set of the strings */ public ArraySet<String> getShortcutTargetsLocked(@UserShortcutType int shortcutType) { + return new ArraySet<>(getShortcutTargetsInternalLocked(shortcutType)); + } + private ArraySet<String> getShortcutTargetsInternalLocked(@UserShortcutType int shortcutType) { if (shortcutType == UserShortcutType.HARDWARE) { return mAccessibilityShortcutKeyTargets; } else if (shortcutType == UserShortcutType.SOFTWARE) { return mAccessibilityButtonTargets; } else if (shortcutType == UserShortcutType.QUICK_SETTINGS) { - return getA11yQsTargets(); + return mAccessibilityQsTargets; } else if ((shortcutType == UserShortcutType.TRIPLETAP && isMagnificationSingleFingerTripleTapEnabledLocked()) || ( shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP @@ -795,6 +798,32 @@ class AccessibilityUserState { } /** + * Updates the corresponding shortcut targets with the provided set. + * Tap shortcuts don't operate using sets of targets, + * so trying to update {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP} + * will instead throw an {@code IllegalArgumentException} + * @param newTargets set of targets to replace the existing set. + * @param shortcutType type to be replaced. + * @return {@code true} if the set was changed, or {@code false} if the elements are the same. + * @throws IllegalArgumentException if {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP} is used. + */ + boolean updateShortcutTargetsLocked( + Set<String> newTargets, @UserShortcutType int shortcutType) { + final int mask = UserShortcutType.TRIPLETAP | UserShortcutType.TWOFINGER_DOUBLETAP; + if ((shortcutType & mask) != 0) { + throw new IllegalArgumentException("Tap shortcuts cannot be updated with target sets."); + } + + final Set<String> currentTargets = getShortcutTargetsInternalLocked(shortcutType); + if (newTargets.equals(currentTargets)) { + return false; + } + currentTargets.clear(); + currentTargets.addAll(newTargets); + return true; + } + + /** * Whether or not the given shortcut target is installed in device. * * @param name The shortcut target name @@ -844,8 +873,9 @@ class AccessibilityUserState { ); } - Set<String> targets = getShortcutTargetsLocked(shortcutType); - boolean result = targets.removeIf(name -> { + // getting internal set lets us directly modify targets, as it's not a copy. + Set<String> targets = getShortcutTargetsInternalLocked(shortcutType); + return targets.removeIf(name -> { ComponentName componentName; if (name == null || (componentName = ComponentName.unflattenFromString(name)) == null) { @@ -853,11 +883,6 @@ class AccessibilityUserState { } return componentName.equals(target); }); - if (shortcutType == UserShortcutType.QUICK_SETTINGS) { - updateA11yQsTargetLocked(targets); - } - - return result; } /** @@ -1114,11 +1139,6 @@ class AccessibilityUserState { ); } - public void updateA11yQsTargetLocked(Set<String> targets) { - mAccessibilityQsTargets.clear(); - mAccessibilityQsTargets.addAll(targets); - } - /** * Returns a copy of the targets which has qs shortcut turned on */ diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 55677078f939..d7649dc4802e 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -17,6 +17,7 @@ package com.android.server.appwidget; import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath; +import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot; import static android.content.Context.KEYGUARD_SERVICE; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -166,6 +167,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.function.LongSupplier; class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider, @@ -457,7 +460,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku break; case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: added = true; - // Follow through + // fall through case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); break; @@ -3571,6 +3574,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } out.endTag(null, "gs"); + + if (supportResumeRestoreAfterReboot() + && mBackupRestoreController.requiresPersistenceLocked()) { + AppWidgetXmlUtil.writeBackupRestoreControllerState( + out, mBackupRestoreController.getStateLocked(userId)); + } + out.endDocument(); return true; } catch (IOException e) { @@ -3717,6 +3727,32 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku LoadedWidgetState loadedWidgets = new LoadedWidgetState(widget, hostTag, providerTag); outLoadedWidgets.add(loadedWidgets); + } else if (supportResumeRestoreAfterReboot() + && AppWidgetXmlUtil.TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag)) { + final BackupRestoreController.State s = + AppWidgetXmlUtil.readBackupRestoreControllerState(parser); + if (s == null) { + continue; + } + final Set<String> prunedAppsInFile = s.getPrunedApps(); + if (prunedAppsInFile != null) { + final Set<String> prunedAppsInMemory = mBackupRestoreController + .mPrunedAppsPerUser.get(userId); + if (prunedAppsInMemory == null) { + mBackupRestoreController.mPrunedAppsPerUser.put( + userId, prunedAppsInFile); + } else { + prunedAppsInMemory.addAll(prunedAppsInFile); + } + } + loadUpdateRecords(s.getUpdatesByProvider(), + this::findProviderByTag, + mBackupRestoreController.mUpdatesByProvider::get, + mBackupRestoreController.mUpdatesByProvider::put); + loadUpdateRecords(s.getUpdatesByHost(), + this::findHostByTag, + mBackupRestoreController.mUpdatesByHost::get, + mBackupRestoreController.mUpdatesByHost::put); } } } while (type != XmlPullParser.END_DOCUMENT); @@ -3732,6 +3768,36 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return version; } + private <T> void loadUpdateRecords( + @Nullable final SparseArray< + List<BackupRestoreController.RestoreUpdateRecord>> updatesOnFile, + @NonNull final Function<Integer, T> findKeyByTagCb, + @NonNull final Function<T, List< + BackupRestoreController.RestoreUpdateRecord>> findRecordsCb, + @NonNull final BiConsumer<T, List< + BackupRestoreController.RestoreUpdateRecord>> newRecordsCb) { + if (updatesOnFile == null) { + return; + } + for (int i = 0; i < updatesOnFile.size(); i++) { + final int tag = updatesOnFile.keyAt(i); + final List< + BackupRestoreController.RestoreUpdateRecord + > recordsOnFile = updatesOnFile.get(tag); + if (recordsOnFile == null || recordsOnFile.isEmpty()) { + continue; + } + final T key = findKeyByTagCb.apply(tag); + final List<BackupRestoreController.RestoreUpdateRecord> recordsInMemory = + findRecordsCb.apply(key); + if (recordsInMemory != null) { + recordsInMemory.addAll(recordsOnFile); + } else { + newRecordsCb.accept(key, recordsOnFile); + } + } + } + private void performUpgradeLocked(int fromVersion) { if (fromVersion < CURRENT_VERSION) { Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to " @@ -4674,7 +4740,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } - private static final class Provider { + static final class Provider { ProviderId id; AppWidgetProviderInfo info; @@ -4931,7 +4997,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } - private static final class Host { + static final class Host { HostId id; ArrayList<Widget> widgets = new ArrayList<>(); IAppWidgetHost callbacks; @@ -5250,10 +5316,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku /** * This class encapsulates the backup and restore logic for a user group state. */ - private final class BackupRestoreController { + final class BackupRestoreController { private static final String TAG = "BackupRestoreController"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = AppWidgetServiceImpl.DEBUG; // Version of backed-up widget state. private static final int WIDGET_STATE_VERSION = 2; @@ -5262,16 +5328,31 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // a given package. Keep track of what we've done so far here; the list is // cleared at the start of every system restore pass, but preserved through // any install-time restore operations. + @GuardedBy("AppWidgetServiceImpl.this.mLock") private final SparseArray<Set<String>> mPrunedAppsPerUser = new SparseArray<>(); - private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider = - new HashMap<>(); - private final HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost = - new HashMap<>(); + @GuardedBy("AppWidgetServiceImpl.this.mLock") + final Map<Provider, List<RestoreUpdateRecord>> mUpdatesByProvider = + new ArrayMap<>(); - @GuardedBy("mLock") + @GuardedBy("AppWidgetServiceImpl.this.mLock") + private final Map<Host, List<RestoreUpdateRecord>> mUpdatesByHost = + new ArrayMap<>(); + + @GuardedBy("AppWidgetServiceImpl.this.mLock") private boolean mHasSystemRestoreFinished; + @GuardedBy("AppWidgetServiceImpl.this.mLock") + public boolean requiresPersistenceLocked() { + if (mHasSystemRestoreFinished) { + // No need to persist intermediate states if system restore is already finished. + return false; + } + // If either of the internal states is non-empty, then we need to persist that + return !(mPrunedAppsPerUser.size() == 0 && mUpdatesByProvider.isEmpty() + && mUpdatesByHost.isEmpty()); + } + public List<String> getWidgetParticipants(int userId) { if (DEBUG) { Slog.i(TAG, "Getting widget participants for user: " + userId); @@ -5436,7 +5517,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // If there's no live entry for this provider, add an inactive one // so that widget IDs referring to them can be properly allocated - // Backup and resotre only for the parent profile. + // Backup and restore only for the parent profile. ComponentName componentName = new ComponentName(pkg, cl); Provider p = findProviderLocked(componentName, userId); @@ -5579,9 +5660,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku final UserHandle userHandle = new UserHandle(userId); // Build the providers' broadcasts and send them off - Set<Map.Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries + Set<Map.Entry<Provider, List<RestoreUpdateRecord>>> providerEntries = mUpdatesByProvider.entrySet(); - for (Map.Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) { + for (Map.Entry<Provider, List<RestoreUpdateRecord>> e : providerEntries) { // For each provider there's a list of affected IDs Provider provider = e.getKey(); if (provider.zombie) { @@ -5589,7 +5670,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // We'll be called again when the provider is installed. continue; } - ArrayList<RestoreUpdateRecord> updates = e.getValue(); + List<RestoreUpdateRecord> updates = e.getValue(); final int pending = countPendingUpdates(updates); if (DEBUG) { Slog.i(TAG, "Provider " + provider + " pending: " + pending); @@ -5618,12 +5699,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } // same thing per host - Set<Map.Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries + Set<Map.Entry<Host, List<RestoreUpdateRecord>>> hostEntries = mUpdatesByHost.entrySet(); - for (Map.Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) { + for (Map.Entry<Host, List<RestoreUpdateRecord>> e : hostEntries) { Host host = e.getKey(); if (host.id.uid != UNKNOWN_UID) { - ArrayList<RestoreUpdateRecord> updates = e.getValue(); + List<RestoreUpdateRecord> updates = e.getValue(); final int pending = countPendingUpdates(updates); if (DEBUG) { Slog.i(TAG, "Host " + host + " pending: " + pending); @@ -5714,8 +5795,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return false; } + @GuardedBy("mLock") private void stashProviderRestoreUpdateLocked(Provider provider, int oldId, int newId) { - ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider); + List<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider); if (r == null) { r = new ArrayList<>(); mUpdatesByProvider.put(provider, r); @@ -5732,7 +5814,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku r.add(new RestoreUpdateRecord(oldId, newId)); } - private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash, + private boolean alreadyStashed(List<RestoreUpdateRecord> stash, final int oldId, final int newId) { final int N = stash.size(); for (int i = 0; i < N; i++) { @@ -5744,8 +5826,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return false; } + @GuardedBy("mLock") private void stashHostRestoreUpdateLocked(Host host, int oldId, int newId) { - ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host); + List<RestoreUpdateRecord> r = mUpdatesByHost.get(host); if (r == null) { r = new ArrayList<>(); mUpdatesByHost.put(host, r); @@ -5835,7 +5918,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku || widget.provider.getUserId() == userId); } - private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) { + private int countPendingUpdates(List<RestoreUpdateRecord> updates) { int pending = 0; final int N = updates.size(); for (int i = 0; i < N; i++) { @@ -5847,9 +5930,28 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return pending; } + @GuardedBy("mLock") + @NonNull + private State getStateLocked(final int userId) { + final Set<String> prunedApps = mPrunedAppsPerUser.get(userId); + final SparseArray<List<RestoreUpdateRecord>> updatesByProvider = new SparseArray<>(); + final SparseArray<List<RestoreUpdateRecord>> updatesByHost = new SparseArray<>(); + mUpdatesByProvider.forEach((p, updates) -> { + if (p.getUserId() == userId) { + updatesByProvider.put(p.tag, new ArrayList<>(updates)); + } + }); + mUpdatesByHost.forEach((h, updates) -> { + if (h.getUserId() == userId) { + updatesByHost.put(h.tag, new ArrayList<>(updates)); + } + }); + return new State(prunedApps, updatesByProvider, updatesByHost); + } + // Accumulate a list of updates that affect the given provider for a final // coalesced notification broadcast once restore is over. - private class RestoreUpdateRecord { + static class RestoreUpdateRecord { public int oldId; public int newId; public boolean notified; @@ -5860,6 +5962,45 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku notified = false; } } + + static final class State { + // We need to make sure to wipe the pre-restore widget state only once for + // a given package. Keep track of what we've done so far here; the list is + // cleared at the start of every system restore pass, but preserved through + // any install-time restore operations. + @Nullable + private final Set<String> mPrunedApps; + + @Nullable + private final SparseArray<List<RestoreUpdateRecord>> mUpdatesByProvider; + + @Nullable + private final SparseArray<List<RestoreUpdateRecord>> mUpdatesByHost; + + State( + @Nullable final Set<String> prunedApps, + @Nullable final SparseArray<List<RestoreUpdateRecord>> updatesByProvider, + @Nullable final SparseArray<List<RestoreUpdateRecord>> updatesByHost) { + mPrunedApps = prunedApps; + mUpdatesByProvider = updatesByProvider; + mUpdatesByHost = updatesByHost; + } + + @Nullable + Set<String> getPrunedApps() { + return mPrunedApps; + } + + @Nullable + SparseArray<List<BackupRestoreController.RestoreUpdateRecord>> getUpdatesByProvider() { + return mUpdatesByProvider; + } + + @Nullable + SparseArray<List<BackupRestoreController.RestoreUpdateRecord>> getUpdatesByHost() { + return mUpdatesByHost; + } + } } private class AppWidgetManagerLocal extends AppWidgetManagerInternal { diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java index d781cd8d58d8..ce9130ad5cbf 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java @@ -22,17 +22,24 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.os.Build; import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; import android.util.SizeF; import android.util.Slog; +import android.util.SparseArray; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; /** @@ -65,6 +72,16 @@ public class AppWidgetXmlUtil { private static final String ATTR_DESCRIPTION_RES = "description_res"; private static final String ATTR_PROVIDER_INHERITANCE = "provider_inheritance"; private static final String ATTR_OS_FINGERPRINT = "os_fingerprint"; + static final String TAG_BACKUP_RESTORE_CONTROLLER_STATE = "br"; + private static final String TAG_PRUNED_APPS = "pruned_apps"; + private static final String ATTR_TAG = "tag"; + private static final String ATTR_PACKAGE_NAMES = "pkgs"; + private static final String TAG_PROVIDER_UPDATES = "provider_updates"; + private static final String TAG_HOST_UPDATES = "host_updates"; + private static final String TAG_RECORD = "record"; + private static final String ATTR_OLD_ID = "old_id"; + private static final String ATTR_NEW_ID = "new_id"; + private static final String ATTR_NOTIFIED = "notified"; private static final String SIZE_SEPARATOR = ","; /** @@ -165,4 +182,168 @@ public class AppWidgetXmlUtil { return null; } } + + /** + * Persists {@link AppWidgetServiceImpl.BackupRestoreController.State} to disk as XML. + * See {@link #readBackupRestoreControllerState(TypedXmlPullParser)} for example XML. + * + * @param out XML serializer + * @param state {@link AppWidgetServiceImpl.BackupRestoreController.State} of + * intermediate states to be persisted as xml to resume restore after reboot. + */ + static void writeBackupRestoreControllerState( + @NonNull final TypedXmlSerializer out, + @NonNull final AppWidgetServiceImpl.BackupRestoreController.State state) + throws IOException { + Objects.requireNonNull(out); + Objects.requireNonNull(state); + out.startTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE); + final Set<String> prunedApps = state.getPrunedApps(); + if (prunedApps != null && !prunedApps.isEmpty()) { + out.startTag(null, TAG_PRUNED_APPS); + out.attribute(null, ATTR_PACKAGE_NAMES, String.join(",", prunedApps)); + out.endTag(null, TAG_PRUNED_APPS); + } + final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> + updatesByProvider = state.getUpdatesByProvider(); + if (updatesByProvider != null) { + writeUpdateRecords(out, TAG_PROVIDER_UPDATES, updatesByProvider); + } + final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> + updatesByHost = state.getUpdatesByHost(); + if (updatesByHost != null) { + writeUpdateRecords(out, TAG_HOST_UPDATES, updatesByHost); + } + out.endTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE); + } + + private static void writeUpdateRecords(@NonNull final TypedXmlSerializer out, + @NonNull final String outerTag, @NonNull final SparseArray<List< + AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> records) + throws IOException { + for (int i = 0; i < records.size(); i++) { + final int tag = records.keyAt(i); + out.startTag(null, outerTag); + out.attributeInt(null, ATTR_TAG, tag); + final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> entries = + records.get(tag); + for (AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord entry : entries) { + out.startTag(null, TAG_RECORD); + out.attributeInt(null, ATTR_OLD_ID, entry.oldId); + out.attributeInt(null, ATTR_NEW_ID, entry.newId); + out.attributeBoolean(null, ATTR_NOTIFIED, entry.notified); + out.endTag(null, TAG_RECORD); + } + out.endTag(null, outerTag); + } + } + + /** + * Parses {@link AppWidgetServiceImpl.BackupRestoreController.State} from xml. + * + * <pre> + * {@code + * <?xml version="1.0"?> + * <br> + * <pruned_apps pkgs="com.example.app1,com.example.app2,com.example.app3" /> + * <provider_updates tag="0"> + * <record old_id="10" new_id="0" notified="false" /> + * </provider_updates> + * <provider_updates tag="1"> + * <record old_id="9" new_id="1" notified="true" /> + * </provider_updates> + * <provider_updates tag="2"> + * <record old_id="8" new_id="2" notified="false" /> + * </provider_updates> + * <host_updates tag="0"> + * <record old_id="10" new_id="0" notified="false" /> + * </host_updates> + * <host_updates tag="1"> + * <record old_id="9" new_id="1" notified="true" /> + * </host_updates> + * <host_updates tag="2"> + * <record old_id="8" new_id="2" notified="false" /> + * </host_updates> + * </br> + * } + * </pre> + * + * @param parser XML parser + * @return {@link AppWidgetServiceImpl.BackupRestoreController.State} of intermediate states + * in {@link AppWidgetServiceImpl.BackupRestoreController}, so that backup & restore can be + * resumed after reboot. + */ + @Nullable + static AppWidgetServiceImpl.BackupRestoreController.State + readBackupRestoreControllerState(@NonNull final TypedXmlPullParser parser) { + Objects.requireNonNull(parser); + int type; + String tag = null; + final Set<String> prunedApps = new ArraySet<>(1); + final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> + updatesByProviders = new SparseArray<>(); + final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> + updatesByHosts = new SparseArray<>(); + + try { + do { + type = parser.next(); + if (type != XmlPullParser.START_TAG) { + continue; + } + tag = parser.getName(); + switch (tag) { + case TAG_PRUNED_APPS: + final String packages = + parser.getAttributeValue(null, ATTR_PACKAGE_NAMES); + prunedApps.addAll(Arrays.asList(packages.split(","))); + break; + case TAG_PROVIDER_UPDATES: + updatesByProviders.put(parser.getAttributeInt(null, ATTR_TAG), + parseRestoreUpdateRecords(parser)); + break; + case TAG_HOST_UPDATES: + updatesByHosts.put(parser.getAttributeInt(null, ATTR_TAG), + parseRestoreUpdateRecords(parser)); + break; + default: + break; + } + } while (type != XmlPullParser.END_DOCUMENT + && (!TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag) + || type != XmlPullParser.END_TAG)); + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "error parsing state", e); + return null; + } + return new AppWidgetServiceImpl.BackupRestoreController.State( + prunedApps, updatesByProviders, updatesByHosts); + } + + @NonNull + private static List< + AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord + > parseRestoreUpdateRecords(@NonNull final TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + int type; + String tag; + final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> ret = + new ArrayList<>(); + do { + type = parser.next(); + tag = parser.getName(); + if (tag.equals(TAG_RECORD) && type == XmlPullParser.START_TAG) { + final int oldId = parser.getAttributeInt(null, ATTR_OLD_ID); + final int newId = parser.getAttributeInt(null, ATTR_NEW_ID); + final boolean notified = parser.getAttributeBoolean( + null, ATTR_NOTIFIED); + final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord record = + new AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord( + oldId, newId); + record.notified = notified; + ret.add(record); + } + } while (tag.equals(TAG_RECORD)); + return ret; + } } diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java index 3fbd8560b82c..b3a2da40ff4d 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java @@ -19,7 +19,6 @@ package com.android.server.companion.association; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; -import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR; import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR; import static android.content.ComponentName.createRelative; import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -183,7 +182,7 @@ public class AssociationRequestsProcessor { String errorMessage = "3p apps are not allowed to create associations on watch."; Slog.e(TAG, errorMessage); try { - callback.onFailure(errorMessage); + callback.onFailure(RESULT_INTERNAL_ERROR); } catch (RemoteException e) { // ignored } @@ -252,8 +251,9 @@ public class AssociationRequestsProcessor { } catch (SecurityException e) { // Since, at this point the caller is our own UI, we need to catch the exception on // forward it back to the application via the callback. + Slog.e(TAG, e.getMessage()); try { - callback.onFailure(e.getMessage()); + callback.onFailure(RESULT_INTERNAL_ERROR); } catch (RemoteException ignore) { } return; @@ -378,7 +378,7 @@ public class AssociationRequestsProcessor { // Send the association back via the app's callback if (callback != null) { try { - callback.onFailure(REASON_INTERNAL_ERROR); + callback.onFailure(RESULT_INTERNAL_ERROR); } catch (RemoteException ignore) { } } diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java index ce4067cf3786..ef39846f4750 100644 --- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java +++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java @@ -192,7 +192,8 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen for (UserInfo user : aliveUsers) { int userId = user.getUserHandle().getIdentifier(); int appUid = queryUidFromPackageName(userId, packageName); - if (mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(appUid)) { + if (mVirtualDeviceManagerInternal != null + && mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(appUid)) { if (data == null) { data = new InjectionSessionData(); data.appUid = appUid; diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java index cf4818060aa8..065568573558 100644 --- a/services/companion/java/com/android/server/companion/virtual/SensorController.java +++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java @@ -229,6 +229,10 @@ public class SensorController { Slog.e(TAG, "No sensor callback configured for sensor handle " + handle); return BAD_VALUE; } + if (mVdmInternal == null) { + Slog.e(TAG, "Virtual Device Manager is not enabled."); + return BAD_VALUE; + } VirtualSensor sensor = mVdmInternal.getVirtualSensor(mVirtualDeviceId, handle); if (sensor == null) { Slog.e(TAG, "No sensor found for deviceId=" + mVirtualDeviceId @@ -285,6 +289,10 @@ public class SensorController { Slog.e(TAG, "No runtime sensor callback configured."); return BAD_VALUE; } + if (mVdmInternal == null) { + Slog.e(TAG, "Virtual Device Manager is not enabled."); + return BAD_VALUE; + } VirtualSensor sensor = mVdmInternal.getVirtualSensor(mVirtualDeviceId, sensorHandle); if (sensor == null) { Slog.e(TAG, "No sensor found for deviceId=" + mVirtualDeviceId diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index b64aa8a1cbc9..ea6351baf597 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -364,7 +364,7 @@ public class ContextualSearchManagerService extends SystemService { } @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) - private int invokeContextualSearchIntent(Intent launchIntent) { + private int invokeContextualSearchIntent(Intent launchIntent, final int userId) { // Contextual search starts with a frozen screen - so we launch without // any system animations or starting window. final ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(mContext, @@ -372,7 +372,7 @@ public class ContextualSearchManagerService extends SystemService { opts.setDisableStartingWindow(true); return mAtmInternal.startActivityWithScreenshot(launchIntent, mContext.getPackageName(), Binder.getCallingUid(), Binder.getCallingPid(), null, - opts.toBundle(), Binder.getCallingUserHandle().getIdentifier()); + opts.toBundle(), userId); } private void enforcePermission(@NonNull final String func) { @@ -446,6 +446,8 @@ public class ContextualSearchManagerService extends SystemService { synchronized (this) { if (DEBUG_USER) Log.d(TAG, "startContextualSearch"); enforcePermission("startContextualSearch"); + final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); + mAssistDataRequester.cancel(); // Creates a new CallbackToken at mToken and an expiration handler. issueToken(); @@ -455,7 +457,7 @@ public class ContextualSearchManagerService extends SystemService { Binder.withCleanCallingIdentity(() -> { Intent launchIntent = getContextualSearchIntent(entrypoint, mToken); if (launchIntent != null) { - int result = invokeContextualSearchIntent(launchIntent); + int result = invokeContextualSearchIntent(launchIntent, callingUserId); if (DEBUG_USER) Log.d(TAG, "Launch result: " + result); } }); diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 256f2b33f3e4..361b818260f1 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -63,7 +63,7 @@ option java_package com.android.server # NotificationManagerService.java # --------------------------- # when a NotificationManager.notify is called. status: 0=post, 1=update, 2=ignored -2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3),(status|1) +2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3),(status|1),(app_provided|1) # when someone tries to cancel a notification, the notification manager sometimes # calls this with flags too 2751 notification_cancel (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(required_flags|1),(forbidden_flags|1),(reason|1|5),(listener|3) diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index b35959f1a6e8..19279a887d11 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -104,6 +104,7 @@ import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.DiskInfo; @@ -1180,6 +1181,7 @@ class StorageManagerService extends IStorageManager.Stub private void onUserUnlocking(int userId) { Slog.d(TAG, "onUserUnlocking " + userId); + Trace.instant(Trace.TRACE_TAG_SYSTEM_SERVER, "SMS.onUserUnlocking: " + userId); if (userId != UserHandle.USER_SYSTEM) { // Check if this user shares media with another user @@ -1466,6 +1468,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void onVolumeCreated(String volId, int type, String diskId, String partGuid, int userId) { + Trace.instant(Trace.TRACE_TAG_SYSTEM_SERVER, + "SMS.onVolumeCreated: " + volId + ", " + userId); synchronized (mLock) { final DiskInfo disk = mDisks.get(diskId); final VolumeInfo vol = new VolumeInfo(volId, type, disk, partGuid); @@ -2352,6 +2356,7 @@ class StorageManagerService extends IStorageManager.Stub private void mount(VolumeInfo vol) { try { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "SMS.mount: " + vol.id); // TODO(b/135341433): Remove cautious logging when FUSE is stable Slog.i(TAG, "Mounting volume " + vol); extendWatchdogTimeout("#mount might be slow"); @@ -2363,6 +2368,8 @@ class StorageManagerService extends IStorageManager.Stub vol.internalPath = internalPath; ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd); try { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + "SMS.startFuseFileSystem: " + vol.id); mStorageSessionController.onVolumeMount(pfd, vol); return true; } catch (ExternalStorageServiceException e) { @@ -2375,6 +2382,7 @@ class StorageManagerService extends IStorageManager.Stub TimeUnit.SECONDS.toMillis(nextResetSeconds)); return false; } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); try { pfd.close(); } catch (Exception e) { @@ -2386,6 +2394,8 @@ class StorageManagerService extends IStorageManager.Stub Slog.i(TAG, "Mounted volume " + vol); } catch (Exception e) { Slog.wtf(TAG, e); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index affe2980532b..45ab62dde880 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -730,7 +730,7 @@ public class ActivityManagerService extends IActivityManager.Stub /** Whether some specified important processes are allowed to use FIFO priority. */ boolean mAllowSpecifiedFifoScheduling = true; - @GuardedBy("this") + @GuardedBy("mStrictModeCallbacks") private final SparseArray<IUnsafeIntentStrictModeCallback> mStrictModeCallbacks = new SparseArray<>(); @@ -5550,9 +5550,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i=0; i<intents.length; i++) { Intent intent = intents[i]; if (intent != null) { - if (intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } + intent.prepareToEnterSystemServer(); if (type == ActivityManager.INTENT_SENDER_BROADCAST && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { throw new IllegalArgumentException( @@ -5585,7 +5583,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } intents[i] = new Intent(intent); - intents[i].removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); } } if (resolvedTypes != null && resolvedTypes.length != intents.length) { @@ -9538,18 +9535,20 @@ public class ActivityManagerService extends IActivityManager.Stub * @param callback The binder used to communicate the violations. */ @Override - public synchronized void registerStrictModeCallback(IBinder callback) { - int callingPid = Binder.getCallingPid(); - mStrictModeCallbacks.put(callingPid, - IUnsafeIntentStrictModeCallback.Stub.asInterface(callback)); - try { - callback.linkToDeath(() -> { - synchronized (ActivityManagerService.this) { - mStrictModeCallbacks.remove(callingPid); - } - }, 0); - } catch (RemoteException e) { - mStrictModeCallbacks.remove(callingPid); + public void registerStrictModeCallback(IBinder callback) { + final int callingPid = Binder.getCallingPid(); + synchronized (mStrictModeCallbacks) { + mStrictModeCallbacks.put(callingPid, + IUnsafeIntentStrictModeCallback.Stub.asInterface(callback)); + try { + callback.linkToDeath(() -> { + synchronized (mStrictModeCallbacks) { + mStrictModeCallbacks.remove(callingPid); + } + }, 0); + } catch (RemoteException e) { + mStrictModeCallbacks.remove(callingPid); + } } } @@ -10238,19 +10237,6 @@ public class ActivityManagerService extends IActivityManager.Stub addStartInfoTimestampInternal(key, timestampNs, userId, callingUid); } - @Override - public void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, - long framePresentedTimeNs) { - int callingUid = Binder.getCallingUid(); - int userId = UserHandle.getUserId(callingUid); - addStartInfoTimestampInternal( - ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME, - renderThreadDrawStartTimeNs, userId, callingUid); - addStartInfoTimestampInternal( - ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE, - framePresentedTimeNs, userId, callingUid); - } - private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) { mProcessList.getAppStartInfoTracker().addTimestampToStart( Settings.getPackageNameForUid(mContext, uid), @@ -13961,12 +13947,7 @@ public class ActivityManagerService extends IActivityManager.Stub enforceNotIsolatedCaller("startService"); enforceAllowedToStartOrBindServiceIfSdkSandbox(service); if (service != null) { - // Refuse possible leaked file descriptors - if (service.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - // Remove existing mismatch flag so it can be properly updated later - service.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); + service.prepareToEnterSystemServer(); } if (callingPackage == null) { @@ -14203,12 +14184,7 @@ public class ActivityManagerService extends IActivityManager.Stub enforceAllowedToStartOrBindServiceIfSdkSandbox(service); if (service != null) { - // Refuse possible leaked file descriptors - if (service.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - // Remove existing mismatch flag so it can be properly updated later - service.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); + service.prepareToEnterSystemServer(); } if (callingPackage == null) { @@ -16242,12 +16218,7 @@ public class ActivityManagerService extends IActivityManager.Stub final Intent verifyBroadcastLocked(Intent intent) { if (intent != null) { - // Refuse possible leaked file descriptors - if (intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - // Remove existing mismatch flag so it can be properly updated later - intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); + intent.prepareToEnterSystemServer(); } int flags = intent.getFlags(); @@ -19938,7 +19909,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void triggerUnsafeIntentStrictMode(int callingPid, int type, Intent intent) { final IUnsafeIntentStrictModeCallback callback; final Intent i = intent.cloneFilter(); - synchronized (ActivityManagerService.this) { + synchronized (mStrictModeCallbacks) { callback = mStrictModeCallbacks.get(callingPid); } if (callback != null) { @@ -19946,7 +19917,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { callback.onUnsafeIntent(type, i); } catch (RemoteException e) { - synchronized (ActivityManagerService.this) { + synchronized (mStrictModeCallbacks) { mStrictModeCallbacks.remove(callingPid); } } diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 4a7ad31cba3e..3042b2a50d50 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -1195,67 +1195,31 @@ public final class AppStartInfoTracker { // Records are sorted newest to oldest, grab record at index 0. ApplicationStartInfo startInfo = mInfos.get(0); - - if (!isAddTimestampAllowed(startInfo, key, timestampNs)) { - return; - } - - startInfo.addStartupTimestamp(key, timestampNs); - - if (key == ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME - && android.app.Flags.appStartInfoTimestamps()) { - startInfo.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); - checkCompletenessAndCallback(startInfo); - } - } - - private boolean isAddTimestampAllowed(ApplicationStartInfo startInfo, int key, - long timestampNs) { int startupState = startInfo.getStartupState(); // If startup state is error then don't accept any further timestamps. if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) { if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps."); - return false; + return; } - Map<Integer, Long> timestamps = startInfo.getStartupTimestamps(); - - if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { - switch (key) { - case ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN: - // Allowed, continue to confirm it's not already added. - break; - case ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME: - Long firstFrameTimeNs = timestamps - .get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME); - if (firstFrameTimeNs == null) { - // This should never happen. State can't be first frame drawn if first - // frame timestamp was not provided. - return false; - } - - if (timestampNs > firstFrameTimeNs) { - // Initial renderthread frame has to occur before first frame. - return false; - } - - // Allowed, continue to confirm it's not already added. - break; - case ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE: - // Allowed, continue to confirm it's not already added. - break; - default: - return false; + // If startup state is first frame drawn then only accept fully drawn timestamp. + if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN + && key != ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) { + if (DEBUG) { + Slog.d(TAG, "Startup state is first frame drawn and timestamp is not fully " + + "drawn, not accepting new timestamps."); } + return; } - if (timestamps.get(key) != null) { - // Timestamp should not occur more than once for a given start. - return false; - } + startInfo.addStartupTimestamp(key, timestampNs); - return true; + if (key == ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME + && android.app.Flags.appStartInfoTimestamps()) { + startInfo.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); + checkCompletenessAndCallback(startInfo); + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 1ac37ad99ada..0c14a1cf84cd 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -130,6 +130,7 @@ import com.android.server.power.stats.BatteryUsageStatsProvider; import com.android.server.power.stats.BluetoothPowerStatsProcessor; import com.android.server.power.stats.CameraPowerStatsProcessor; import com.android.server.power.stats.CpuPowerStatsProcessor; +import com.android.server.power.stats.CustomEnergyConsumerPowerStatsProcessor; import com.android.server.power.stats.FlashlightPowerStatsProcessor; import com.android.server.power.stats.GnssPowerStatsProcessor; import com.android.server.power.stats.MobileRadioPowerStatsProcessor; @@ -574,6 +575,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub AggregatedPowerStatsConfig.STATE_PROCESS_STATE) .setProcessor( new GnssPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver)); + + config.trackCustomPowerComponents(CustomEnergyConsumerPowerStatsProcessor::new) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE); return config; } @@ -665,6 +675,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub BatteryConsumer.POWER_COMPONENT_CAMERA, Flags.streamlinedMiscBatteryStats()); + // By convention POWER_COMPONENT_ANY represents custom Energy Consumers + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_ANY, + Flags.streamlinedMiscBatteryStats()); + mWorker.systemServicesReady(); mStats.systemServicesReady(mContext); mCpuWakeupStats.systemServicesReady(); diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index a8b9e43bdae4..4ff13679ed5c 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -674,7 +674,9 @@ public class ContentProviderHelper { if (conn != null) { conn.waiting = true; } - cpr.wait(wait); + if (wait > 0) { + cpr.wait(wait); + } if (cpr.provider == null) { timedOut = true; break; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index fa0e2ca3b2d4..30efa3e87fc6 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -2335,6 +2335,14 @@ class UserController implements Handler.Callback { // Never stop system user return; } + synchronized(mLock) { + final UserState uss = mStartedUsers.get(oldUserId); + if (uss == null || uss.state == UserState.STATE_STOPPING + || uss.state == UserState.STATE_SHUTDOWN) { + // We've stopped (or are stopping) the user anyway, so don't bother scheduling. + return; + } + } if (oldUserId == mInjector.getUserManagerInternal().getMainUserId()) { // MainUser is currently special for things like Docking, so we'll exempt it for now. Slogf.i(TAG, "Exempting user %d from being stopped due to inactivity by virtue " @@ -2371,6 +2379,12 @@ class UserController implements Handler.Callback { // We'll soon want to switch to this user, so don't kill it now. return; } + final UserInfo currentOrTargetUser = getCurrentUserLU(); + if (currentOrTargetUser != null && currentOrTargetUser.isGuest()) { + // Don't kill any background users for the sake of a Guest. Just reschedule instead. + scheduleStopOfBackgroundUser(userId); + return; + } Slogf.i(TAG, "Stopping background user %d due to inactivity", userId); stopUsersLU(userId, /* allowDelayedLocking= */ true, null, null); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 1bb792227798..f61bd6040658 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2790,8 +2790,9 @@ public class AppOpsService extends IAppOpsService.Stub { * have information on them. */ private static boolean isOpAllowedForUid(int uid) { + int appId = UserHandle.getAppId(uid); return Flags.runtimePermissionAppopsMappingEnabled() - && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID); + && (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID); } @Override diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index bce183050a74..27fda15d5a05 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -414,7 +414,8 @@ public class AudioDeviceBroker { } if (!mScoManagedByAudio) { boolean isBtScoRequested = isBluetoothScoRequested(); - if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { + if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive() + || !mBtHelper.isBluetoothScoRequestedInternally())) { if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: " + uid); @@ -1148,13 +1149,14 @@ public class AudioDeviceBroker { } /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { - if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource); - } synchronized (mBluetoothAudioStateLock) { + boolean isBtScoRequested = isBluetoothScoRequested(); + Log.i(TAG, "setBluetoothScoOn: " + on + ", mBluetoothScoOn: " + + mBluetoothScoOn + ", isBtScoRequested: " + isBtScoRequested + + ", from: " + eventSource); mBluetoothScoOn = on; updateAudioHalBluetoothState(); - postUpdateCommunicationRouteClient(isBluetoothScoRequested(), eventSource); + postUpdateCommunicationRouteClient(isBtScoRequested, eventSource); } } diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java index 76191bbe1a78..14eae8da0417 100644 --- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java @@ -16,10 +16,20 @@ package com.android.server.audio; +import static android.Manifest.permission.ACCESS_ULTRASOUND; +import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.CALL_AUDIO_INTERCEPTION; +import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; +import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT; +import static android.Manifest.permission.CAPTURE_MEDIA_OUTPUT; +import static android.Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT; +import static android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT; import static android.Manifest.permission.MODIFY_AUDIO_ROUTING; +import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS; +import static android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS; import static android.Manifest.permission.MODIFY_PHONE_STATE; import static android.Manifest.permission.RECORD_AUDIO; +import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import android.annotation.Nullable; import android.os.RemoteException; @@ -52,10 +62,21 @@ public class AudioServerPermissionProvider { static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; static { + MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO; MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING; + MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_SETTINGS] = MODIFY_AUDIO_SETTINGS; MONITORED_PERMS[PermissionEnum.MODIFY_PHONE_STATE] = MODIFY_PHONE_STATE; - MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO; + MONITORED_PERMS[PermissionEnum.MODIFY_DEFAULT_AUDIO_EFFECTS] = MODIFY_DEFAULT_AUDIO_EFFECTS; + MONITORED_PERMS[PermissionEnum.WRITE_SECURE_SETTINGS] = WRITE_SECURE_SETTINGS; MONITORED_PERMS[PermissionEnum.CALL_AUDIO_INTERCEPTION] = CALL_AUDIO_INTERCEPTION; + MONITORED_PERMS[PermissionEnum.ACCESS_ULTRASOUND] = ACCESS_ULTRASOUND; + MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_OUTPUT] = CAPTURE_AUDIO_OUTPUT; + MONITORED_PERMS[PermissionEnum.CAPTURE_MEDIA_OUTPUT] = CAPTURE_MEDIA_OUTPUT; + MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_HOTWORD] = CAPTURE_AUDIO_HOTWORD; + MONITORED_PERMS[PermissionEnum.CAPTURE_TUNER_AUDIO_INPUT] = CAPTURE_TUNER_AUDIO_INPUT; + MONITORED_PERMS[PermissionEnum.CAPTURE_VOICE_COMMUNICATION_OUTPUT] = + CAPTURE_VOICE_COMMUNICATION_OUTPUT; + MONITORED_PERMS[PermissionEnum.BLUETOOTH_CONNECT] = BLUETOOTH_CONNECT; } private final Object mLock = new Object(); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 684cb245f62b..6d1983e75388 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -783,9 +783,11 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_OUT_HDMI_EARC )); + private final Object mAbsoluteVolumeDeviceInfoMapLock = new Object(); // Devices where the framework sends a full scale audio signal, and controls the volume of // the external audio system separately. // For possible volume behaviors, see {@link AudioManager.AbsoluteDeviceVolumeBehavior}. + @GuardedBy("mAbsoluteVolumeDeviceInfoMapLock") Map<Integer, AbsoluteVolumeDeviceInfo> mAbsoluteVolumeDeviceInfoMap = new ArrayMap<>(); /** @@ -3715,9 +3717,8 @@ public class AudioService extends IAudioService.Stub int oldIndex = mStreamStates[streamType].getIndex(device); // Check if the volume adjustment should be handled by an absolute volume controller instead - if (isAbsoluteVolumeDevice(device) - && (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) { - AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device); + if (isAbsoluteVolumeDevice(device) && (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) { + final AbsoluteVolumeDeviceInfo info = getAbsoluteVolumeDeviceInfo(device); if (info.mHandlesVolumeAdjustment) { dispatchAbsoluteVolumeAdjusted(streamType, info, oldIndex, direction, keyEventMode); @@ -3784,7 +3785,7 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10); } else if (isAbsoluteVolumeDevice(device) && (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) { - AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device); + final AbsoluteVolumeDeviceInfo info = getAbsoluteVolumeDeviceInfo(device); dispatchAbsoluteVolumeChanged(streamType, info, newIndex); } @@ -4787,7 +4788,7 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10); } else if (isAbsoluteVolumeDevice(device) && ((flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0)) { - AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device); + final AbsoluteVolumeDeviceInfo info = getAbsoluteVolumeDeviceInfo(device); dispatchAbsoluteVolumeChanged(streamType, info, index); } @@ -7575,7 +7576,8 @@ public class AudioService extends IAudioService.Stub if (register) { AbsoluteVolumeDeviceInfo info = new AbsoluteVolumeDeviceInfo( device, volumes, cb, handlesVolumeAdjustment, deviceVolumeBehavior); - AbsoluteVolumeDeviceInfo oldInfo = mAbsoluteVolumeDeviceInfoMap.get(deviceOut); + final AbsoluteVolumeDeviceInfo oldInfo = getAbsoluteVolumeDeviceInfo(deviceOut); + boolean volumeBehaviorChanged = (oldInfo == null) || (oldInfo.mDeviceVolumeBehavior != deviceVolumeBehavior); if (volumeBehaviorChanged) { @@ -7735,8 +7737,10 @@ public class AudioService extends IAudioService.Stub if (mAbsVolumeMultiModeCaseDevices.contains(audioSystemDeviceOut)) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE; } - if (mAbsoluteVolumeDeviceInfoMap.containsKey(audioSystemDeviceOut)) { - return mAbsoluteVolumeDeviceInfoMap.get(audioSystemDeviceOut).mDeviceVolumeBehavior; + synchronized (mAbsoluteVolumeDeviceInfoMapLock) { + if (mAbsoluteVolumeDeviceInfoMap.containsKey(audioSystemDeviceOut)) { + return mAbsoluteVolumeDeviceInfoMap.get(audioSystemDeviceOut).mDeviceVolumeBehavior; + } } if (isA2dpAbsoluteVolumeDevice(audioSystemDeviceOut) @@ -11774,10 +11778,12 @@ public class AudioService extends IAudioService.Stub } private Set<Integer> getAbsoluteVolumeDevicesWithBehavior(int behavior) { - return mAbsoluteVolumeDeviceInfoMap.entrySet().stream() - .filter(entry -> entry.getValue().mDeviceVolumeBehavior == behavior) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); + synchronized (mAbsoluteVolumeDeviceInfoMapLock) { + return mAbsoluteVolumeDeviceInfoMap.entrySet().stream() + .filter(entry -> entry.getValue().mDeviceVolumeBehavior == behavior) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } } private String dumpDeviceTypes(@NonNull Set<Integer> deviceTypes) { @@ -14270,14 +14276,26 @@ public class AudioService extends IAudioService.Stub } /** + * Returns the input device which uses absolute volume behavior, including its variants, + * or {@code null} if there is no mapping for the device type + */ + @Nullable + private AbsoluteVolumeDeviceInfo getAbsoluteVolumeDeviceInfo(int deviceType) { + synchronized (mAbsoluteVolumeDeviceInfoMapLock) { + return mAbsoluteVolumeDeviceInfoMap.get(deviceType); + } + } + + /** * Returns whether the input device uses absolute volume behavior, including its variants. * For included volume behaviors, see {@link AudioManager.AbsoluteDeviceVolumeBehavior}. - * - * This is distinct from Bluetooth A2DP absolute volume behavior + * <p>This is distinct from Bluetooth A2DP absolute volume behavior * ({@link #isA2dpAbsoluteVolumeDevice}). */ private boolean isAbsoluteVolumeDevice(int deviceType) { - return mAbsoluteVolumeDeviceInfoMap.containsKey(deviceType); + synchronized (mAbsoluteVolumeDeviceInfoMapLock) { + return mAbsoluteVolumeDeviceInfoMap.containsKey(deviceType); + } } /** @@ -14389,7 +14407,9 @@ public class AudioService extends IAudioService.Stub + AudioDeviceVolumeManager.volumeBehaviorName(info.mDeviceVolumeBehavior) ); } - mAbsoluteVolumeDeviceInfoMap.put(audioSystemDeviceOut, info); + synchronized (mAbsoluteVolumeDeviceInfoMapLock) { + mAbsoluteVolumeDeviceInfoMap.put(audioSystemDeviceOut, info); + } } private AbsoluteVolumeDeviceInfo removeAudioSystemDeviceOutFromAbsVolumeDevices( @@ -14398,7 +14418,10 @@ public class AudioService extends IAudioService.Stub Log.d(TAG, "Removing DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + " from mAbsoluteVolumeDeviceInfoMap"); } - return mAbsoluteVolumeDeviceInfoMap.remove(audioSystemDeviceOut); + + synchronized (mAbsoluteVolumeDeviceInfoMapLock) { + return mAbsoluteVolumeDeviceInfoMap.remove(audioSystemDeviceOut); + } } //==================== diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 991f94ba6f73..8008717fea35 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -378,7 +378,6 @@ public class BtHelper { /*package*/ synchronized void onReceiveBtEvent(Intent intent) { final String action = intent.getAction(); - Log.i(TAG, "onReceiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState); if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, android.bluetooth.BluetoothDevice.class); @@ -405,6 +404,7 @@ public class BtHelper { private void onScoAudioStateChanged(int state) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; + Log.i(TAG, "onScoAudioStateChanged state: " + state + " mScoAudioState: " + mScoAudioState); if (mDeviceBroker.isScoManagedByAudio()) { switch (state) { case BluetoothHeadset.STATE_AUDIO_CONNECTED: @@ -488,6 +488,11 @@ public class BtHelper { == BluetoothHeadset.STATE_AUDIO_CONNECTED; } + /*package*/ synchronized boolean isBluetoothScoRequestedInternally() { + return mScoAudioState == SCO_STATE_ACTIVE_INTERNAL + || mScoAudioState == SCO_STATE_ACTIVATE_REQ; + } + // @GuardedBy("mDeviceBroker.mSetModeLock") @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode, diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 0e81eb914697..8e8a0378c111 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -31,8 +31,8 @@ import android.app.IActivityManager; import android.app.UserSwitchObserver; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; -import android.content.ContentResolver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; @@ -233,6 +233,7 @@ public class BiometricService extends SystemService { private static final boolean DEFAULT_KEYGUARD_ENABLED = true; private static final boolean DEFAULT_APP_ENABLED = true; private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false; + private static final boolean DEFAULT_MANDATORY_BIOMETRICS_STATUS = false; // Some devices that shipped before S already have face-specific settings. Instead of // migrating, which is complicated, let's just keep using the existing settings. @@ -253,6 +254,8 @@ public class BiometricService extends SystemService { Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED); private final Uri BIOMETRIC_APP_ENABLED = Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED); + private final Uri MANDATORY_BIOMETRICS_ENABLED = + Settings.Secure.getUriFor(Settings.Secure.MANDATORY_BIOMETRICS); private final ContentResolver mContentResolver; private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks; @@ -260,6 +263,7 @@ public class BiometricService extends SystemService { private final Map<Integer, Boolean> mBiometricEnabledOnKeyguard = new HashMap<>(); private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>(); private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>(); + private final Map<Integer, Boolean> mMandatoryBiometricsEnabled = new HashMap<>(); /** * Creates a content observer. @@ -281,6 +285,9 @@ public class BiometricService extends SystemService { mUseLegacyFaceOnlySettings = Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q && hasFace && !hasFingerprint; + mMandatoryBiometricsEnabled.put(context.getUserId(), Settings.Secure.getIntForUser( + mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS, + DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0, context.getUserId()) != 0); updateContentObserver(); } @@ -311,6 +318,10 @@ public class BiometricService extends SystemService { false /* notifyForDescendants */, this /* observer */, UserHandle.USER_ALL); + mContentResolver.registerContentObserver(MANDATORY_BIOMETRICS_ENABLED, + false /* notifyForDescendants */, + this /* observer */, + UserHandle.USER_ALL); } @Override @@ -353,6 +364,12 @@ public class BiometricService extends SystemService { Settings.Secure.BIOMETRIC_APP_ENABLED, DEFAULT_APP_ENABLED ? 1 : 0 /* default */, userId) != 0); + } else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) { + mMandatoryBiometricsEnabled.put(userId, Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.MANDATORY_BIOMETRICS, + DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0 /* default */, + userId) != 0); } } @@ -394,6 +411,11 @@ public class BiometricService extends SystemService { } } + public boolean getMandatoryBiometricsEnabledForUser(int userId) { + return mMandatoryBiometricsEnabled.getOrDefault(userId, + DEFAULT_MANDATORY_BIOMETRICS_STATUS); + } + void notifyEnabledOnKeyguardCallbacks(int userId) { List<EnabledOnKeyguardCallback> callbacks = mCallbacks; for (int i = 0; i < callbacks.size(); i++) { diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index f085647c9676..b9e6563a7d6a 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -29,11 +29,13 @@ import android.app.trust.ITrustManager; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.PromptInfo; import android.os.RemoteException; import android.util.Pair; import android.util.Slog; +import com.android.internal.R; import com.android.server.biometrics.sensors.LockoutTracker; import java.lang.annotation.Retention; @@ -59,6 +61,7 @@ class PreAuthInfo { static final int BIOMETRIC_LOCKOUT_TIMED = 10; static final int BIOMETRIC_LOCKOUT_PERMANENT = 11; static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12; + static final int MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR = 13; private static final String TAG = "BiometricService/PreAuthInfo"; final boolean credentialRequested; // Sensors that can be used for this request (e.g. strong enough, enrolled, enabled). @@ -73,12 +76,14 @@ class PreAuthInfo { private final boolean mBiometricRequested; private final int mBiometricStrengthRequested; private final BiometricCameraManager mBiometricCameraManager; + private final boolean mOnlyMandatoryBiometricsRequested; private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, - boolean confirmationRequested, boolean ignoreEnrollmentState, int userId, - Context context, BiometricCameraManager biometricCameraManager) { + PromptInfo promptInfo, int userId, Context context, + BiometricCameraManager biometricCameraManager, + boolean isOnlyMandatoryBiometricsRequested) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; mBiometricCameraManager = biometricCameraManager; @@ -87,10 +92,11 @@ class PreAuthInfo { this.eligibleSensors = eligibleSensors; this.ineligibleSensors = ineligibleSensors; this.credentialAvailable = credentialAvailable; - this.confirmationRequested = confirmationRequested; - this.ignoreEnrollmentState = ignoreEnrollmentState; + this.confirmationRequested = promptInfo.isConfirmationRequested(); + this.ignoreEnrollmentState = promptInfo.isIgnoreEnrollmentState(); this.userId = userId; this.context = context; + this.mOnlyMandatoryBiometricsRequested = isOnlyMandatoryBiometricsRequested; } static PreAuthInfo create(ITrustManager trustManager, @@ -102,7 +108,16 @@ class PreAuthInfo { BiometricCameraManager biometricCameraManager) throws RemoteException { - final boolean confirmationRequested = promptInfo.isConfirmationRequested(); + final boolean isOnlyMandatoryBiometricsRequested = promptInfo.getAuthenticators() + == BiometricManager.Authenticators.MANDATORY_BIOMETRICS; + + if (dropCredentialFallback(promptInfo.getAuthenticators(), + settingObserver.getMandatoryBiometricsEnabledForUser(userId), + trustManager)) { + promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG); + promptInfo.setNegativeButtonText(context.getString(R.string.cancel)); + } + final boolean biometricRequested = Utils.isBiometricRequested(promptInfo); final int requestedStrength = Utils.getPublicBiometricStrength(promptInfo); final boolean credentialRequested = Utils.isCredentialRequested(promptInfo); @@ -150,8 +165,27 @@ class PreAuthInfo { } return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, - eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, - promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager); + eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo, userId, + context, biometricCameraManager, isOnlyMandatoryBiometricsRequested); + } + + private static boolean dropCredentialFallback(int authenticators, + boolean isMandatoryBiometricsEnabled, ITrustManager trustManager) { + final boolean isMandatoryBiometricsRequested = + (authenticators & BiometricManager.Authenticators.MANDATORY_BIOMETRICS) + == BiometricManager.Authenticators.MANDATORY_BIOMETRICS; + if (Flags.mandatoryBiometrics() && isMandatoryBiometricsEnabled + && isMandatoryBiometricsRequested) { + try { + final boolean isInSignificantPlace = trustManager.isInSignificantPlace(); + return !isInSignificantPlace; + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception while trying to check " + + "if user is in a trusted location."); + } + } + + return false; } /** @@ -353,6 +387,25 @@ class PreAuthInfo { status = CREDENTIAL_NOT_ENROLLED; } } + } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested) { + if (!eligibleSensors.isEmpty()) { + for (BiometricSensor sensor : eligibleSensors) { + modality |= sensor.modality; + } + + if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face, credential is unavailable, + // and the face sensor privacy is enabled then return + // BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will not be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } + } else { + status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; + } } else if (mBiometricRequested) { if (!eligibleSensors.isEmpty()) { for (BiometricSensor sensor : eligibleSensors) { @@ -509,7 +562,8 @@ class PreAuthInfo { CREDENTIAL_NOT_ENROLLED, BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_PERMANENT, - BIOMETRIC_SENSOR_PRIVACY_ENABLED}) + BIOMETRIC_SENSOR_PRIVACY_ENABLED, + MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR}) @Retention(RetentionPolicy.SOURCE) @interface AuthenticatorStatus { } diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 4af30a923b4c..df29ca45930e 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -140,6 +140,14 @@ public class Utils { } /** + * @param authenticators composed of one or more values from {@link Authenticators} + * @return true if mandatory biometrics is requested + */ + static boolean isMandatoryBiometricsRequested(@Authenticators.Types int authenticators) { + return (authenticators & Authenticators.MANDATORY_BIOMETRICS) != 0; + } + + /** * @param promptInfo should be first processed by * {@link #combineAuthenticatorBundles(PromptInfo)} * @return true if device credential is allowed. @@ -242,7 +250,8 @@ public class Utils { // Check if any of the non-biometric and non-credential bits are set. If so, this is // invalid. final int testBits = ~(Authenticators.DEVICE_CREDENTIAL - | Authenticators.BIOMETRIC_MIN_STRENGTH); + | Authenticators.BIOMETRIC_MIN_STRENGTH + | Authenticators.MANDATORY_BIOMETRICS); if ((authenticators & testBits) != 0) { Slog.e(BiometricService.TAG, "Non-biometric, non-credential bits found." + " Authenticators: " + authenticators); @@ -259,6 +268,8 @@ public class Utils { return true; } else if (biometricBits == Authenticators.BIOMETRIC_WEAK) { return true; + } else if (isMandatoryBiometricsRequested(authenticators)) { + return true; } Slog.e(BiometricService.TAG, "Unsupported biometric flags. Authenticators: " diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 0afca92d78f7..73aa14ba016f 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -251,7 +251,7 @@ public class ClipboardService extends SystemService { } private void registerVirtualDeviceListener() { - if (mVirtualDeviceListener != null) { + if (mVdm == null || mVirtualDeviceListener != null) { return; } mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() { @@ -891,7 +891,8 @@ public class ClipboardService extends SystemService { Slog.e(TAG, "RemoteException calling UserManager: " + e); return null; } - if (deviceId != DEVICE_ID_DEFAULT && !mVdm.isValidVirtualDeviceId(deviceId)) { + if (deviceId != DEVICE_ID_DEFAULT + && mVdm != null && !mVdm.isValidVirtualDeviceId(deviceId)) { Slog.w(TAG, "getClipboardLocked called with invalid (possibly released) deviceId " + deviceId); return null; @@ -1467,8 +1468,8 @@ public class ClipboardService extends SystemService { return; } // Don't notify if this access is coming from the privileged app which owns the device. - if (clipboard.deviceId != DEVICE_ID_DEFAULT && mVdmInternal.getDeviceOwnerUid( - clipboard.deviceId) == uid) { + if (clipboard.deviceId != DEVICE_ID_DEFAULT && mVdmInternal != null + && mVdmInternal.getDeviceOwnerUid(clipboard.deviceId) == uid) { return; } // Don't notify if already notified for this uid and clip. @@ -1519,7 +1520,7 @@ public class ClipboardService extends SystemService { private ArraySet<Context> getToastContexts(Clipboard clipboard) throws IllegalStateException { ArraySet<Context> contexts = new ArraySet<>(); - if (clipboard.deviceId != DEVICE_ID_DEFAULT) { + if (mVdmInternal != null && clipboard.deviceId != DEVICE_ID_DEFAULT) { DisplayManager displayManager = getContext().getSystemService(DisplayManager.class); int topFocusedDisplayId = mWm.getTopFocusedDisplayId(); diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index f8fd0a0790e0..22b85d45d15e 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -83,9 +83,10 @@ public class PlatformCompat extends IPlatformCompat.Stub { @VisibleForTesting PlatformCompat(Context context, CompatConfig compatConfig, - AndroidBuildClassifier buildClassifier) { + AndroidBuildClassifier buildClassifier, + ChangeReporter changeReporter) { mContext = context; - mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); + mChangeReporter = changeReporter; mCompatConfig = compatConfig; mBuildClassifier = buildClassifier; @@ -96,8 +97,11 @@ public class PlatformCompat extends IPlatformCompat.Stub { @EnforcePermission(LOG_COMPAT_CHANGE) public void reportChange(long changeId, ApplicationInfo appInfo) { super.reportChange_enforcePermission(); - - reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); + reportChangeInternal( + changeId, + appInfo.uid, + appInfo.isSystemApp(), + ChangeReporter.STATE_LOGGED); } @Override @@ -108,7 +112,11 @@ public class PlatformCompat extends IPlatformCompat.Stub { ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo != null) { - reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); + reportChangeInternal( + changeId, + appInfo.uid, + appInfo.isSystemApp(), + ChangeReporter.STATE_LOGGED); } } @@ -117,7 +125,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { public void reportChangeByUid(long changeId, int uid) { super.reportChangeByUid_enforcePermission(); - reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED); + reportChangeInternal(changeId, uid, false, ChangeReporter.STATE_LOGGED); } /** @@ -128,8 +136,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { * @param uid of the user * @param state of the change - enabled/disabled/logged */ - private void reportChangeInternal(long changeId, int uid, int state) { - mChangeReporter.reportChange(uid, changeId, state, true); + private void reportChangeInternal(long changeId, int uid, boolean isKnownSystemApp, int state) { + mChangeReporter.reportChange(uid, changeId, state, isKnownSystemApp, true); } @Override @@ -190,7 +198,11 @@ public class PlatformCompat extends IPlatformCompat.Stub { if (appInfo != null) { boolean isTargetingLatestSdk = mCompatConfig.isChangeTargetingLatestSdk(c, appInfo.targetSdkVersion); - mChangeReporter.reportChange(appInfo.uid, changeId, state, isTargetingLatestSdk); + mChangeReporter.reportChange(appInfo.uid, + changeId, + state, + appInfo.isSystemApp(), + isTargetingLatestSdk); } return enabled; } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 2d5f38e2dd8c..e686779e4ccd 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1548,16 +1548,20 @@ public final class DisplayManagerService extends SystemService { int flags = virtualDisplayConfig.getFlags(); if (virtualDevice != null) { final VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class); - try { - if (!vdm.isValidVirtualDeviceId(virtualDevice.getDeviceId())) { - throw new SecurityException("Invalid virtual device"); + if (vdm != null) { + try { + if (!vdm.isValidVirtualDeviceId(virtualDevice.getDeviceId())) { + throw new SecurityException("Invalid virtual device"); + } + } catch (RemoteException ex) { + throw new SecurityException("Unable to validate virtual device"); + } + final VirtualDeviceManagerInternal localVdm = + getLocalService(VirtualDeviceManagerInternal.class); + if (localVdm != null) { + flags |= localVdm.getBaseVirtualDisplayFlags(virtualDevice); } - } catch (RemoteException ex) { - throw new SecurityException("Unable to validate virtual device"); } - final VirtualDeviceManagerInternal localVdm = - getLocalService(VirtualDeviceManagerInternal.class); - flags |= localVdm.getBaseVirtualDisplayFlags(virtualDevice); } if (surface != null && surface.isSingleBuffered()) { diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java index 0fef55da5749..a188e79e4c8b 100644 --- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java +++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java @@ -84,6 +84,14 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display } @Override + public void cancelBlockScreenOn() { + if (mDisplayOffloader == null) { + return; + } + mDisplayOffloader.cancelBlockScreenOn(); + } + + @Override public float[] getAutoBrightnessLevels(int mode) { if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) { throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 65a729a0293e..c298bbf5a551 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -587,7 +587,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mUniqueDisplayId, mThermalBrightnessThrottlingDataId, logicalDisplay.getPowerThrottlingDataIdLocked(), - mDisplayDeviceConfig), mContext, flags, mSensorManager); + mDisplayDeviceConfig, + mDisplayId), mContext, flags, mSensorManager); // Seed the cached brightness saveBrightnessInfo(getScreenBrightnessSetting()); mAutomaticBrightnessStrategy = @@ -892,7 +893,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // will call updatePowerState if needed. mBrightnessClamperController.onDisplayChanged( new BrightnessClamperController.DisplayDeviceData(uniqueId, - thermalBrightnessThrottlingDataId, powerThrottlingDataId, config)); + thermalBrightnessThrottlingDataId, powerThrottlingDataId, + config, mDisplayId)); if (changed) { updatePowerState(); @@ -2108,6 +2110,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0); } + private void cancelUnblockScreenOnByDisplayOffload() { + if (mDisplayOffloadSession == null) { + return; + } + if (mPendingScreenOnUnblockerByDisplayOffload != null) { + // Already unblocked. + return; + } + mDisplayOffloadSession.cancelBlockScreenOn(); + } + private boolean setScreenState(int state, @Display.StateReason int reason) { return setScreenState(state, reason, false /*reportOnly*/); } @@ -2124,6 +2137,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call blockScreenOnByDisplayOffload(mDisplayOffloadSession); } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) { // No longer turning screen on, so unblock previous screen on blocking immediately. + if (mFlags.isOffloadSessionCancelBlockScreenOnEnabled()) { + cancelUnblockScreenOnByDisplayOffload(); + } unblockScreenOnByDisplayOffload(); mScreenTurningOnWasBlockedByDisplayOffload = false; } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 101ad307f50a..220640270e02 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -23,23 +23,17 @@ import static com.android.server.display.brightness.clamper.BrightnessClamper.Ty import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Resources; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.HandlerExecutor; import android.os.PowerManager; -import android.os.SystemClock; import android.provider.DeviceConfig; import android.provider.DeviceConfigInterface; import android.util.IndentingPrintWriter; import android.util.Slog; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig; @@ -50,30 +44,22 @@ import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.config.SensorData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; -import com.android.server.display.utils.AmbientFilter; -import com.android.server.display.utils.AmbientFilterFactory; -import com.android.server.display.utils.DebugUtils; -import com.android.server.display.utils.SensorUtils; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; /** * Clampers controller, all in DisplayControllerHandler */ public class BrightnessClamperController { private static final String TAG = "BrightnessClamperController"; - // To enable these logs, run: - // 'adb shell setprop persist.log.tag.BrightnessClamperController DEBUG && adb reboot' - private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); - public static final float INVALID_LUX = -1f; private final DeviceConfigParameterProvider mDeviceConfigParameterProvider; private final Handler mHandler; - private final SensorManager mSensorManager; + private final LightSensorController mLightSensorController; + private final ClamperChangeListener mClamperChangeListenerExternal; private final Executor mExecutor; private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers; @@ -85,70 +71,49 @@ public class BrightnessClamperController { private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; @Nullable private Type mClamperType = null; - private final SensorEventListener mLightSensorListener; - private Sensor mRegisteredLightSensor = null; - private Sensor mLightSensor; - private String mLightSensorType; - private String mLightSensorName; - private AmbientFilter mAmbientFilter; - private final DisplayDeviceConfig mDisplayDeviceConfig; - private final Resources mResources; - private final int mLightSensorRate; - - private final Injector mInjector; + private boolean mClamperApplied = false; + private final LightSensorController.LightSensorListener mLightSensorListener = + new LightSensorController.LightSensorListener() { + @Override + public void onAmbientLuxChange(float lux) { + mModifiers.forEach(mModifier -> mModifier.setAmbientLux(lux)); + } + }; + public BrightnessClamperController(Handler handler, ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context, DisplayManagerFlags flags, SensorManager sensorManager) { - this(null, handler, clamperChangeListener, data, context, flags, sensorManager); + this(new Injector(), handler, clamperChangeListener, data, context, flags, sensorManager); } @VisibleForTesting BrightnessClamperController(Injector injector, Handler handler, ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context, DisplayManagerFlags flags, SensorManager sensorManager) { - mInjector = injector == null ? new Injector() : injector; - mDeviceConfigParameterProvider = mInjector.getDeviceConfigParameterProvider(); + mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider(); mHandler = handler; - mSensorManager = sensorManager; - mDisplayDeviceConfig = data.mDisplayDeviceConfig; - mLightSensorListener = new SensorEventListener() { - @Override - public void onSensorChanged(SensorEvent event) { - long now = SystemClock.elapsedRealtime(); - mAmbientFilter.addValue(TimeUnit.NANOSECONDS.toMillis(event.timestamp), - event.values[0]); - final float lux = mAmbientFilter.getEstimate(now); - mModifiers.forEach(mModifier -> mModifier.setAmbientLux(lux)); - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // unused - } - }; + mLightSensorController = injector.getLightSensorController(sensorManager, context, + mLightSensorListener, mHandler); mClamperChangeListenerExternal = clamperChangeListener; mExecutor = new HandlerExecutor(handler); - mResources = context.getResources(); - mLightSensorRate = context.getResources().getInteger( - R.integer.config_autoBrightnessLightSensorRate); Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap; - ClamperChangeListener clamperChangeListenerInternal = () -> { if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) { mHandler.post(clamperChangeRunnableInternal); } }; - mClampers = mInjector.getClampers(handler, clamperChangeListenerInternal, data, flags, + mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags, context); - mModifiers = mInjector.getModifiers(flags, context, handler, clamperChangeListener, - data.mDisplayDeviceConfig, mSensorManager); + mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener, + data.mDisplayDeviceConfig); mOnPropertiesChangedListener = properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); + mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId()); start(); } @@ -156,7 +121,9 @@ public class BrightnessClamperController { * Should be called when display changed. Forwards the call to individual clampers */ public void onDisplayChanged(DisplayDeviceData data) { + mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId()); mClampers.forEach(clamper -> clamper.onDisplayChanged(data)); + adjustLightSensorSubscription(); } /** @@ -184,9 +151,9 @@ public class BrightnessClamperController { } if (displayState != STATE_ON) { - unregisterSensorListener(); + mLightSensorController.stop(); } else { - maybeRegisterLightSensor(); + adjustLightSensorSubscription(); } for (int i = 0; i < mModifiers.size(); i++) { @@ -231,9 +198,8 @@ public class BrightnessClamperController { writer.println(" mBrightnessCap: " + mBrightnessCap); writer.println(" mClamperType: " + mClamperType); writer.println(" mClamperApplied: " + mClamperApplied); - writer.println(" mLightSensor=" + mLightSensor); - writer.println(" mRegisteredLightSensor=" + mRegisteredLightSensor); IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " "); + mLightSensorController.dump(ipw); mClampers.forEach(clamper -> clamper.dump(ipw)); mModifiers.forEach(modifier -> modifier.dump(ipw)); } @@ -245,6 +211,7 @@ public class BrightnessClamperController { public void stop() { mDeviceConfigParameterProvider.removeOnPropertiesChangedListener( mOnPropertiesChangedListener); + mLightSensorController.stop(); mClampers.forEach(BrightnessClamper::stop); mModifiers.forEach(BrightnessStateModifier::stop); } @@ -281,10 +248,15 @@ public class BrightnessClamperController { if (!mClampers.isEmpty()) { mDeviceConfigParameterProvider.addOnPropertiesChangedListener( mExecutor, mOnPropertiesChangedListener); - reloadLightSensorData(mDisplayDeviceConfig); - mLightSensor = mInjector.getLightSensor( - mSensorManager, mLightSensorType, mLightSensorName); - maybeRegisterLightSensor(); + } + adjustLightSensorSubscription(); + } + + private void adjustLightSensorSubscription() { + if (mModifiers.stream().anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) { + mLightSensorController.restart(); + } else { + mLightSensorController.stop(); } } @@ -323,7 +295,7 @@ public class BrightnessClamperController { List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context, Handler handler, ClamperChangeListener listener, - DisplayDeviceConfig displayDeviceConfig, SensorManager sensorManager) { + DisplayDeviceConfig displayDeviceConfig) { List<BrightnessStateModifier> modifiers = new ArrayList<>(); modifiers.add(new DisplayDimModifier(context)); modifiers.add(new BrightnessLowPowerModeModifier()); @@ -335,11 +307,12 @@ public class BrightnessClamperController { return modifiers; } - Sensor getLightSensor(SensorManager sensorManager, String type, String name) { - return SensorUtils.findSensor(sensorManager, type, - name, Sensor.TYPE_LIGHT); + LightSensorController getLightSensorController(SensorManager sensorManager, + Context context, LightSensorController.LightSensorListener listener, + Handler handler) { + return new LightSensorController(sensorManager, context.getResources(), + listener, handler); } - } /** @@ -354,17 +327,21 @@ public class BrightnessClamperController { private final String mThermalThrottlingDataId; @NonNull private final String mPowerThrottlingDataId; - + @NonNull private final DisplayDeviceConfig mDisplayDeviceConfig; + private final int mDisplayId; + public DisplayDeviceData(@NonNull String uniqueDisplayId, @NonNull String thermalThrottlingDataId, @NonNull String powerThrottlingDataId, - @NonNull DisplayDeviceConfig displayDeviceConfig) { + @NonNull DisplayDeviceConfig displayDeviceConfig, + int displayId) { mUniqueDisplayId = uniqueDisplayId; mThermalThrottlingDataId = thermalThrottlingDataId; mPowerThrottlingDataId = powerThrottlingDataId; mDisplayDeviceConfig = displayDeviceConfig; + mDisplayId = displayId; } @@ -412,55 +389,18 @@ public class BrightnessClamperController { } @NonNull + @Override public SensorData getTempSensor() { return mDisplayDeviceConfig.getTempSensor(); } - } - - private void maybeRegisterLightSensor() { - if (mModifiers.stream().noneMatch(BrightnessStateModifier::shouldListenToLightSensor)) { - return; - } - - if (mRegisteredLightSensor == mLightSensor) { - return; - } - if (mRegisteredLightSensor != null) { - unregisterSensorListener(); - } - - mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, mResources); - mSensorManager.registerListener(mLightSensorListener, - mLightSensor, mLightSensorRate * 1000, mHandler); - mRegisteredLightSensor = mLightSensor; - - if (DEBUG) { - Slog.d(TAG, "maybeRegisterLightSensor"); - } - } - - private void unregisterSensorListener() { - mSensorManager.unregisterListener(mLightSensorListener); - mRegisteredLightSensor = null; - mModifiers.forEach(mModifier -> mModifier.setAmbientLux(INVALID_LUX)); // set lux to invalid - if (DEBUG) { - Slog.d(TAG, "unregisterSensorListener"); + @NonNull + SensorData getAmbientLightSensor() { + return mDisplayDeviceConfig.getAmbientLightSensor(); } - } - private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) { - // The displayDeviceConfig (ddc) contains display specific preferences. When loaded, - // it naturally falls back to the global config.xml. - if (displayDeviceConfig != null - && displayDeviceConfig.getAmbientLightSensor() != null) { - // This covers both the ddc and the config.xml fallback - mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type; - mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name; - } else if (mLightSensorName == null && mLightSensorType == null) { - mLightSensorType = mResources.getString( - com.android.internal.R.string.config_displayLightSensorType); - mLightSensorName = ""; + int getDisplayId() { + return mDisplayId; } } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java b/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java new file mode 100644 index 000000000000..d89dd28c4a89 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.brightness.clamper; + +import android.annotation.Nullable; +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Slog; +import android.view.Display; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.config.SensorData; +import com.android.server.display.utils.AmbientFilter; +import com.android.server.display.utils.AmbientFilterFactory; +import com.android.server.display.utils.DebugUtils; +import com.android.server.display.utils.SensorUtils; + +import java.io.PrintWriter; +import java.util.concurrent.TimeUnit; + +/** + * Manages light sensor subscription and notifies its listener about ambient lux changes + */ +public class LightSensorController { + private static final String TAG = "LightSensorController"; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.LightSensorController DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); + static final float INVALID_LUX = -1f; + + private final SensorManager mSensorManager; + private final LightSensorListener mLightSensorListener; + private final Handler mHandler; + private final Injector mInjector; + private final AmbientFilter mAmbientFilter; + + private Sensor mLightSensor; + private Sensor mRegisteredLightSensor = null; + private final int mLightSensorRate; + + private final SensorEventListener mLightSensorEventListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + long now = mInjector.getTime(); + mAmbientFilter.addValue(TimeUnit.NANOSECONDS.toMillis(event.timestamp), + event.values[0]); + final float lux = mAmbientFilter.getEstimate(now); + mLightSensorListener.onAmbientLuxChange(lux); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // unused + } + }; + + LightSensorController(SensorManager sensorManager, Resources resources, + LightSensorListener listener, Handler handler) { + this(sensorManager, resources, listener, handler, new Injector()); + } + + @VisibleForTesting + LightSensorController(SensorManager sensorManager, Resources resources, + LightSensorListener listener, Handler handler, Injector injector) { + mSensorManager = sensorManager; + mLightSensorRate = injector.getLightSensorRate(resources); + mAmbientFilter = injector.getAmbientFilter(resources); + mLightSensorListener = listener; + mHandler = handler; + mInjector = injector; + } + + void restart() { + if (mRegisteredLightSensor == mLightSensor) { + return; + } + if (mRegisteredLightSensor != null) { + stop(); + } + if (mLightSensor == null) { + return; + } + + mSensorManager.registerListener(mLightSensorEventListener, + mLightSensor, mLightSensorRate * 1000, mHandler); + mRegisteredLightSensor = mLightSensor; + + if (DEBUG) { + Slog.d(TAG, "restart"); + } + } + + void stop() { + if (mRegisteredLightSensor == null) { + return; + } + mSensorManager.unregisterListener(mLightSensorEventListener); + mRegisteredLightSensor = null; + mAmbientFilter.clear(); + mLightSensorListener.onAmbientLuxChange(INVALID_LUX); + if (DEBUG) { + Slog.d(TAG, "stop"); + } + } + + void configure(SensorData sensorData, int displayId) { + final int fallbackType = displayId == Display.DEFAULT_DISPLAY + ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK; + mLightSensor = mInjector.getLightSensor(mSensorManager, sensorData, fallbackType); + } + + void dump(PrintWriter writer) { + writer.println("LightSensorController"); + writer.println(" mLightSensor=" + mLightSensor); + writer.println(" mRegisteredLightSensor=" + mRegisteredLightSensor); + } + + static class Injector { + @Nullable + Sensor getLightSensor(SensorManager sensorManager, SensorData sensorData, + int fallbackType) { + return SensorUtils.findSensor(sensorManager, sensorData, fallbackType); + } + + AmbientFilter getAmbientFilter(Resources resources) { + return AmbientFilterFactory.createBrightnessFilter(TAG, resources); + } + + int getLightSensorRate(Resources resources) { + return resources.getInteger(R.integer.config_autoBrightnessLightSensorRate); + } + + // should be consistent with SensorEvent.timestamp + long getTime() { + return SystemClock.elapsedRealtime(); + } + } + + interface LightSensorListener { + void onAmbientLuxChange(float ambientLux); + } +} diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java index b43b35ba50bc..ddb091dd170d 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java @@ -15,6 +15,8 @@ */ package com.android.server.display.brightness.strategy; +import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; @@ -133,14 +135,20 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 // We are still in the process of updating the power state, so there's no need to trigger // an update again switchMode(targetDisplayState, /* sendUpdate= */ false); - final boolean autoBrightnessEnabledInDoze = - allowAutoBrightnessWhileDozingConfig && Display.isDozeState(targetDisplayState); + + // If the policy is POLICY_DOZE and the display state is STATE_ON, auto-brightness should + // only be enabled if the config allows it + final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig + && policy == POLICY_DOZE && targetDisplayState != Display.STATE_OFF; + mIsAutoBrightnessEnabled = shouldUseAutoBrightness() - && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze) + && ((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE) + || autoBrightnessEnabledInDoze) && brightnessReason != BrightnessReason.REASON_OVERRIDE && mAutomaticBrightnessController != null; mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness() - && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze); + && !((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE) + || autoBrightnessEnabledInDoze); final int autoBrightnessState = mIsAutoBrightnessEnabled && brightnessReason != BrightnessReason.REASON_FOLLOWER ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java index 4d9c18ab72fe..c87872cb24cb 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java @@ -15,6 +15,8 @@ */ package com.android.server.display.brightness.strategy; +import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessConfiguration; @@ -107,14 +109,19 @@ public class AutomaticBrightnessStrategy2 { public void setAutoBrightnessState(int targetDisplayState, boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy, float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) { - final boolean autoBrightnessEnabledInDoze = - allowAutoBrightnessWhileDozingConfig && Display.isDozeState(targetDisplayState); + // If the policy is POLICY_DOZE and the display state is STATE_ON, auto-brightness should + // only be enabled if the config allows it + final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig + && policy == POLICY_DOZE && targetDisplayState != Display.STATE_OFF; + mIsAutoBrightnessEnabled = shouldUseAutoBrightness() - && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze) + && ((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE) + || autoBrightnessEnabledInDoze) && brightnessReason != BrightnessReason.REASON_OVERRIDE && mAutomaticBrightnessController != null; mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness() - && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze); + && !((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE) + || autoBrightnessEnabledInDoze); final int autoBrightnessState = mIsAutoBrightnessEnabled && brightnessReason != BrightnessReason.REASON_FOLLOWER ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index f56d8036f870..41d18cd6bf12 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -179,6 +179,11 @@ public class DisplayManagerFlags { Flags::offloadDozeOverrideHoldsWakelock ); + private final FlagState mOffloadSessionCancelBlockScreenOn = + new FlagState( + Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON, + Flags::offloadSessionCancelBlockScreenOn); + /** * @return {@code true} if 'port' is allowed in display layout configuration file. */ @@ -352,6 +357,10 @@ public class DisplayManagerFlags { return mOffloadDozeOverrideHoldsWakelock.isEnabled(); } + public boolean isOffloadSessionCancelBlockScreenOnEnabled() { + return mOffloadSessionCancelBlockScreenOn.isEnabled(); + } + /** * @return Whether to ignore preferredRefreshRate app request conversion to display mode or not */ @@ -399,6 +408,7 @@ public class DisplayManagerFlags { pw.println(" " + mIgnoreAppPreferredRefreshRate); pw.println(" " + mSynthetic60hzModes); pw.println(" " + mOffloadDozeOverrideHoldsWakelock); + pw.println(" " + mOffloadSessionCancelBlockScreenOn); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 95d0ca381f77..1ea5c0b23be1 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -299,3 +299,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "offload_session_cancel_block_screen_on" + namespace: "wear_frameworks" + description: "Flag for DisplayPowerController to start notifying DisplayOffloadSession about cancelling screen on blocker." + bug: "331725519" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig index 9bbcb0c99e8e..d387828872e9 100644 --- a/services/core/java/com/android/server/flags/services.aconfig +++ b/services/core/java/com/android/server/flags/services.aconfig @@ -28,3 +28,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "wear_frameworks" + name: "optional_background_install_control" + description: "Enable BackgroundInstallControl based on system feature to prevent it from starting on form factors." + bug: "340928990" +} diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index 7d485271e35c..b77f47d640e9 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -69,7 +69,6 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @NonNull private final ImeTargetVisibilityPolicy mImeTargetVisibilityPolicy; - DefaultImeVisibilityApplier(InputMethodManagerService service) { mService = service; mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); @@ -80,8 +79,9 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @Override public void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { - final IInputMethodInvoker curMethod = mService.getCurMethodLocked(); + @SoftInputShowHideReason int reason, @UserIdInt int userId) { + final var bindingController = mService.getInputMethodBindingController(userId); + final IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null) { if (DEBUG) { Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken @@ -99,7 +99,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { mService.mImeBindingState.mFocusedWindowSoftInputMode)); } mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason, - statsToken); + statsToken, userId); } } } @@ -107,8 +107,10 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override public void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, - ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - final IInputMethodInvoker curMethod = mService.getCurMethodLocked(); + ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, + @UserIdInt int userId) { + final var bindingController = mService.getInputMethodBindingController(userId); + final IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null) { // The IME will report its visible state again after the following message finally // delivered to the IME process as an IPC. Hence the inconsistency between @@ -130,7 +132,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { mService.mImeBindingState.mFocusedWindowSoftInputMode)); } mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason, - statsToken); + statsToken, userId); } } } @@ -180,7 +182,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { setImeVisibilityOnFocusedWindowClient(false); } else { mService.hideCurrentInputLocked(windowToken, statsToken, - 0 /* flags */, null /* resultReceiver */, reason); + 0 /* flags */, null /* resultReceiver */, reason); } break; case STATE_HIDE_IME_NOT_ALWAYS: @@ -199,14 +201,14 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { } else { mService.showCurrentInputLocked(windowToken, statsToken, InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN, - null /* resultReceiver */, reason); + null /* resultReceiver */, reason); } break; case STATE_SHOW_IME_SNAPSHOT: - showImeScreenshot(windowToken, displayIdToShowIme); + showImeScreenshot(windowToken, displayIdToShowIme, userId); break; case STATE_REMOVE_IME_SNAPSHOT: - removeImeScreenshot(displayIdToShowIme); + removeImeScreenshot(displayIdToShowIme, userId); break; default: throw new IllegalArgumentException("Invalid IME visibility state: " + state); @@ -215,10 +217,11 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId) { + public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId, + @UserIdInt int userId) { if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) { mService.onShowHideSoftInputRequested(false /* show */, imeTarget, - SHOW_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */); + SHOW_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */, userId); return true; } return false; @@ -226,11 +229,11 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public boolean removeImeScreenshot(int displayId) { + public boolean removeImeScreenshot(int displayId, @UserIdInt int userId) { if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) { mService.onShowHideSoftInputRequested(false /* show */, mService.mImeBindingState.mFocusedWindow, - REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */); + REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */, userId); return true; } return false; diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java index 62adb25954b4..a6b07dedfa4a 100644 --- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java +++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java @@ -19,7 +19,6 @@ package com.android.server.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -34,24 +33,13 @@ final class HardwareKeyboardShortcutController { @GuardedBy("ImfLock.class") private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>(); - @UserIdInt - private final int mUserId; - - @AnyThread - @UserIdInt - int getUserId() { - return mUserId; - } - - HardwareKeyboardShortcutController(@NonNull InputMethodMap methodMap, @UserIdInt int userId) { - mUserId = userId; - reset(methodMap); + HardwareKeyboardShortcutController(@NonNull InputMethodSettings settings) { + update(settings); } @GuardedBy("ImfLock.class") - void reset(@NonNull InputMethodMap methodMap) { + void update(@NonNull InputMethodSettings settings) { mSubtypeHandles.clear(); - final InputMethodSettings settings = InputMethodSettings.create(methodMap, mUserId); final List<InputMethodInfo> inputMethods = settings.getEnabledInputMethodList(); for (int i = 0; i < inputMethods.size(); ++i) { final InputMethodInfo imi = inputMethods.get(i); diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java index a5f9b7a986c9..c1069f2a23d7 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java @@ -33,38 +33,45 @@ interface ImeVisibilityApplier { /** * Performs showing IME on top of the given window. * - * @param showInputToken A token that represents the requester to show IME. - * @param statsToken The token tracking the current IME request. - * @param resultReceiver If non-null, this will be called back to the caller when - * it has processed request to tell what it has done. - * @param reason The reason for requesting to show IME. + * @param showInputToken a token that represents the requester to show IME + * @param statsToken the token tracking the current IME request + * @param resultReceiver if non-null, this will be called back to the caller when + * it has processed request to tell what it has done + * @param reason yhe reason for requesting to show IME + * @param userId the target user when performing show IME */ default void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) {} + @SoftInputShowHideReason int reason, @UserIdInt int userId) { + } /** * Performs hiding IME to the given window * - * @param hideInputToken A token that represents the requester to hide IME. - * @param statsToken The token tracking the current IME request. - * @param resultReceiver If non-null, this will be called back to the caller when - * it has processed request to tell what it has done. - * @param reason The reason for requesting to hide IME. + * @param hideInputToken a token that represents the requester to hide IME + * @param statsToken the token tracking the current IME request + * @param resultReceiver if non-null, this will be called back to the caller when + * it has processed request to tell what it has done + * @param reason the reason for requesting to hide IME + * @param userId the target user when performing hide IME */ default void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, - ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {} + ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, + @UserIdInt int userId) { + } /** * Applies the IME visibility from {@link android.inputmethodservice.InputMethodService} with * according to the given visibility state. * - * @param windowToken The token of a window for applying the IME visibility - * @param statsToken The token tracking the current IME request. - * @param state The new IME visibility state for the applier to handle + * @param windowToken the token of a window for applying the IME visibility + * @param statsToken the token tracking the current IME request + * @param state the new IME visibility state for the applier to handle + * @param userId the target user when applying the IME visibility state */ default void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken, - @ImeVisibilityStateComputer.VisibilityState int state, @UserIdInt int userId) {} + @ImeVisibilityStateComputer.VisibilityState int state, @UserIdInt int userId) { + } /** * Updates the IME Z-ordering relative to the given window. @@ -72,7 +79,7 @@ interface ImeVisibilityApplier { * This used to adjust the IME relative layer of the window during * {@link InputMethodManagerService} is in switching IME clients. * - * @param windowToken The token of a window to update the Z-ordering relative to the IME. + * @param windowToken the token of a window to update the Z-ordering relative to the IME */ default void updateImeLayeringByTarget(IBinder windowToken) { // TODO: add a method in WindowManagerInternal to call DC#updateImeInputAndControlTarget @@ -82,21 +89,24 @@ interface ImeVisibilityApplier { /** * Shows the IME screenshot and attach it to the given IME target window. * - * @param windowToken The token of a window to show the IME screenshot. - * @param displayId The unique id to identify the display - * @return {@code true} if success, {@code false} otherwise. + * @param windowToken the token of a window to show the IME screenshot + * @param displayId the unique id to identify the display + * @param userId the target user when when showing the IME screenshot + * @return {@code true} if success, {@code false} otherwise */ - default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId) { + default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId, + @UserIdInt int userId) { return false; } /** * Removes the IME screenshot on the given display. * - * @param displayId The target display of showing IME screenshot. - * @return {@code true} if success, {@code false} otherwise. + * @param displayId the target display of showing IME screenshot + * @param userId the target user of showing IME screenshot + * @return {@code true} if success, {@code false} otherwise */ - default boolean removeImeScreenshot(int displayId) { + default boolean removeImeScreenshot(int displayId, @UserIdInt int userId) { return false; } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3f296ca7a975..440343d1d0f4 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -381,10 +381,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @NonNull @MultiUserUnawareField private InputMethodSubtypeSwitchingController mSwitchingController; - // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - @NonNull - @MultiUserUnawareField - private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController; @Nullable private StatusBarManagerInternal mStatusBarManagerInternal; @@ -1300,12 +1296,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); - mSwitchingController = - InputMethodSubtypeSwitchingController.createInstanceLocked(context, - settings.getMethodMap(), settings.getUserId()); - mHardwareKeyboardShortcutController = - new HardwareKeyboardShortcutController(settings.getMethodMap(), - settings.getUserId()); + mSwitchingController = new InputMethodSubtypeSwitchingController(context, settings); + getUserData(mCurrentUserId).mHardwareKeyboardShortcutController.update(settings); mMenuController = new InputMethodMenuController(this); mVisibilityStateComputer = new ImeVisibilityStateComputer(this); mVisibilityApplier = new DefaultImeVisibilityApplier(this); @@ -2937,7 +2929,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * </li> * <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li> * <li>{@link #mSwitchingController} is ignored.</li> - * <li>{@link #mHardwareKeyboardShortcutController} is ignored.</li> * <li>{@link #mPreventImeStartupUnlessTextEditor} is ignored.</li> * <li>and so on.</li> * </ul> @@ -2970,6 +2961,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. id = imi.getId(); settings.putSelectedInputMethod(id); } + + final var userData = getUserData(userId); + userData.mHardwareKeyboardShortcutController.update(settings); } @GuardedBy("ImfLock.class") @@ -3047,18 +3041,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // TODO: Instantiate mSwitchingController for each user. if (userId == mSwitchingController.getUserId()) { - mSwitchingController.resetCircularListLocked(settings.getMethodMap()); - } else { - mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, settings.getMethodMap(), userId); - } - // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - if (userId == mHardwareKeyboardShortcutController.getUserId()) { - mHardwareKeyboardShortcutController.reset(settings.getMethodMap()); + mSwitchingController.resetCircularListLocked(settings); } else { - mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - settings.getMethodMap(), userId); + mSwitchingController = new InputMethodSubtypeSwitchingController(mContext, settings); } + getUserData(userId).mHardwareKeyboardShortcutController.update(settings); sendOnNavButtonFlagsChangedLocked(); } @@ -3520,10 +3507,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mVisibilityStateComputer.requestImeVisibility(windowToken, true); + final int userId = mCurrentUserId; // Ensure binding the connection when IME is going to show. - final var bindingController = getInputMethodBindingController(mCurrentUserId); + final var bindingController = getInputMethodBindingController(userId); bindingController.setCurrentMethodVisible(); - final IInputMethodInvoker curMethod = getCurMethodLocked(); + final IInputMethodInvoker curMethod = bindingController.getCurMethod(); ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); final boolean readyToDispatchToIme; if (Flags.deferShowSoftInputUntilSessionCreation()) { @@ -3543,7 +3531,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } mVisibilityApplier.performShowIme(windowToken, statsToken, mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(), - resultReceiver, reason); + resultReceiver, reason, userId); mVisibilityStateComputer.setInputShown(true); return true; } else { @@ -3655,7 +3643,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only // IMMS#InputShown indicates that the software keyboard is shown. // TODO(b/246309664): Clean up IMMS#mImeWindowVis - IInputMethodInvoker curMethod = getCurMethodLocked(); + final int userId = mCurrentUserId; + final var bindingController = getInputMethodBindingController(userId); + IInputMethodInvoker curMethod = bindingController.getCurMethod(); final boolean shouldHideSoftInput = curMethod != null && (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); @@ -3666,11 +3656,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in // the final state. ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE); - mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason); + mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason, + userId); } else { ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE); } - final var bindingController = getInputMethodBindingController(mCurrentUserId); bindingController.setCurrentMethodNotVisible(); mVisibilityStateComputer.clearImeShowFlags(); // Cancel existing statsToken for show IME as we got a hide request. @@ -4736,12 +4726,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. */ @GuardedBy("ImfLock.class") void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken, - @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) { + @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken, + @UserIdInt int userId) { final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken); + final var bindingController = getInputMethodBindingController(userId); final WindowManagerInternal.ImeTargetInfo info = mWindowManagerInternal.onToggleImeRequested( show, mImeBindingState.mFocusedWindow, requestToken, - getCurTokenDisplayIdLocked()); + bindingController.getCurTokenDisplayId()); mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo, info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason, @@ -4928,7 +4920,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController .getSortedInputMethodAndSubtypeList( showAuxSubtypes, isScreenLocked, true /* forImeMenu */, - mContext, settings.getMethodMap(), settings.getUserId()); + mContext, settings); if (imList.isEmpty()) { Slog.w(TAG, "Show switching menu failed, imList is empty," + " showAuxSubtypes: " + showAuxSubtypes @@ -5318,18 +5310,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // TODO: Instantiate mSwitchingController for each user. if (userId == mSwitchingController.getUserId()) { - mSwitchingController.resetCircularListLocked(settings.getMethodMap()); - } else { - mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, settings.getMethodMap(), mCurrentUserId); - } - // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - if (userId == mHardwareKeyboardShortcutController.getUserId()) { - mHardwareKeyboardShortcutController.reset(settings.getMethodMap()); + mSwitchingController.resetCircularListLocked(settings); } else { - mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - settings.getMethodMap(), userId); + mSwitchingController = new InputMethodSubtypeSwitchingController(mContext, settings); } + getUserData(userId).mHardwareKeyboardShortcutController.update(settings); sendOnNavButtonFlagsChangedLocked(); @@ -5640,8 +5625,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final InputMethodSubtypeHandle currentSubtypeHandle = InputMethodSubtypeHandle.of(currentImi, bindingController.getCurrentSubtype()); final InputMethodSubtypeHandle nextSubtypeHandle = - mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle, - direction > 0); + getUserData(userId).mHardwareKeyboardShortcutController.onSubtypeSwitch( + currentSubtypeHandle, direction > 0); if (nextSubtypeHandle == null) { return; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 770e3b267145..f97a5165ebec 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -48,15 +48,20 @@ final class InputMethodSubtypeSwitchingController { private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> { + + @NonNull public final CharSequence mImeName; + @Nullable public final CharSequence mSubtypeName; + @NonNull public final InputMethodInfo mImi; public final int mSubtypeId; public final boolean mIsSystemLocale; public final boolean mIsSystemLanguage; - ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, - InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) { + ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName, + @NonNull InputMethodInfo imi, int subtypeId, @Nullable String subtypeLocale, + @NonNull String systemLocale) { mImeName = imeName; mSubtypeName = subtypeName; mImi = imi; @@ -69,7 +74,6 @@ final class InputMethodSubtypeSwitchingController { if (mIsSystemLocale) { mIsSystemLanguage = true; } else { - // TODO: Use Locale#getLanguage or Locale#toLanguageTag final String systemLanguage = LocaleUtils.getLanguageFromLocaleString( systemLocale); final String subtypeLanguage = LocaleUtils.getLanguageFromLocaleString( @@ -101,8 +105,9 @@ final class InputMethodSubtypeSwitchingController { * <li>{@link #mSubtypeName}</li> * <li>{@link #mImi} with {@link InputMethodInfo#getId()}</li> * </ol> - * Note: this class has a natural ordering that is inconsistent with {@link #equals(Object). - * This method doesn't compare {@link #mSubtypeId} but {@link #equals(Object)} does. + * Note: this class has a natural ordering that is inconsistent with + * {@link #equals(Object)}. This method doesn't compare {@link #mSubtypeId} but + * {@link #equals(Object)} does. * * @param other the object to be compared. * @return a negative integer, zero, or positive integer as this object is less than, equal @@ -155,15 +160,15 @@ final class InputMethodSubtypeSwitchingController { } } + @NonNull static List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList( boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu, - @NonNull Context context, @NonNull InputMethodMap methodMap, - @UserIdInt int userId) { + @NonNull Context context, @NonNull InputMethodSettings settings) { + final int userId = settings.getUserId(); final Context userAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag(); - final InputMethodSettings settings = InputMethodSettings.create(methodMap, userId); final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList(); if (imis.isEmpty()) { @@ -190,7 +195,7 @@ final class InputMethodSubtypeSwitchingController { enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); } final CharSequence imeLabel = imi.loadLabel(userAwareContext.getPackageManager()); - if (enabledSubtypeSet.size() > 0) { + if (!enabledSubtypeSet.isEmpty()) { final int subtypeCount = imi.getSubtypeCount(); if (DEBUG) { Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); @@ -223,14 +228,18 @@ final class InputMethodSubtypeSwitchingController { return imList; } - private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { + private static int calculateSubtypeId(@NonNull InputMethodInfo imi, + @Nullable InputMethodSubtype subtype) { return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) : NOT_A_SUBTYPE_ID; } private static class StaticRotationList { + + @NonNull private final List<ImeSubtypeListItem> mImeSubtypeList; - StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { + + StaticRotationList(@NonNull List<ImeSubtypeListItem> imeSubtypeList) { mImeSubtypeList = imeSubtypeList; } @@ -242,24 +251,22 @@ final class InputMethodSubtypeSwitchingController { * does not have a subtype. * @return The index in the given list. -1 if not found. */ - private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { + private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) { final int currentSubtypeId = calculateSubtypeId(imi, subtype); final int numSubtypes = mImeSubtypeList.size(); for (int i = 0; i < numSubtypes; ++i) { - final ImeSubtypeListItem isli = mImeSubtypeList.get(i); + final ImeSubtypeListItem item = mImeSubtypeList.get(i); // Skip until the current IME/subtype is found. - if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { + if (imi.equals(item.mImi) && item.mSubtypeId == currentSubtypeId) { return i; } } return -1; } + @Nullable public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, - InputMethodInfo imi, InputMethodSubtype subtype) { - if (imi == null) { - return null; - } + @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) { if (mImeSubtypeList.size() <= 1) { return null; } @@ -282,22 +289,24 @@ final class InputMethodSubtypeSwitchingController { return null; } - protected void dump(final Printer pw, final String prefix) { + protected void dump(@NonNull Printer pw, @NonNull String prefix) { final int numSubtypes = mImeSubtypeList.size(); - for (int i = 0; i < numSubtypes; ++i) { - final int rank = i; - final ImeSubtypeListItem item = mImeSubtypeList.get(i); + for (int rank = 0; rank < numSubtypes; ++rank) { + final ImeSubtypeListItem item = mImeSubtypeList.get(rank); pw.println(prefix + "rank=" + rank + " item=" + item); } } } private static class DynamicRotationList { + private static final String TAG = DynamicRotationList.class.getSimpleName(); + @NonNull private final List<ImeSubtypeListItem> mImeSubtypeList; + @NonNull private final int[] mUsageHistoryOfSubtypeListItemIndex; - private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { + private DynamicRotationList(@NonNull List<ImeSubtypeListItem> imeSubtypeListItems) { mImeSubtypeList = imeSubtypeListItems; mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()]; final int numSubtypes = mImeSubtypeList.size(); @@ -314,7 +323,8 @@ final class InputMethodSubtypeSwitchingController { * * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually. */ - private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) { + private int getUsageRank(@NonNull InputMethodInfo imi, + @Nullable InputMethodSubtype subtype) { final int currentSubtypeId = calculateSubtypeId(imi, subtype); final int numItems = mUsageHistoryOfSubtypeListItemIndex.length; for (int usageRank = 0; usageRank < numItems; usageRank++) { @@ -330,7 +340,8 @@ final class InputMethodSubtypeSwitchingController { return -1; } - public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) { + public void onUserAction(@NonNull InputMethodInfo imi, + @Nullable InputMethodSubtype subtype) { final int currentUsageRank = getUsageRank(imi, subtype); // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0 if (currentUsageRank <= 0) { @@ -342,8 +353,9 @@ final class InputMethodSubtypeSwitchingController { mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex; } + @Nullable public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, - InputMethodInfo imi, InputMethodSubtype subtype) { + @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) { int currentUsageRank = getUsageRank(imi, subtype); if (currentUsageRank < 0) { if (DEBUG) { @@ -366,7 +378,7 @@ final class InputMethodSubtypeSwitchingController { return null; } - protected void dump(final Printer pw, final String prefix) { + protected void dump(@NonNull Printer pw, @NonNull String prefix) { for (int rank = 0; rank < mUsageHistoryOfSubtypeListItemIndex.length; ++rank) { final int index = mUsageHistoryOfSubtypeListItemIndex[rank]; final ImeSubtypeListItem item = mImeSubtypeList.get(index); @@ -377,58 +389,52 @@ final class InputMethodSubtypeSwitchingController { @VisibleForTesting public static class ControllerImpl { + + @NonNull private final DynamicRotationList mSwitchingAwareRotationList; + @NonNull private final StaticRotationList mSwitchingUnawareRotationList; - public static ControllerImpl createFrom(final ControllerImpl currentInstance, - final List<ImeSubtypeListItem> sortedEnabledItems) { - DynamicRotationList switchingAwareRotationList = null; - { - final List<ImeSubtypeListItem> switchingAwareImeSubtypes = - filterImeSubtypeList(sortedEnabledItems, - true /* supportsSwitchingToNextInputMethod */); - if (currentInstance != null - && currentInstance.mSwitchingAwareRotationList != null - && Objects.equals( - currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, - switchingAwareImeSubtypes)) { - // Can reuse the current instance. - switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; - } - if (switchingAwareRotationList == null) { - switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); - } + @NonNull + public static ControllerImpl createFrom(@Nullable ControllerImpl currentInstance, + @NonNull List<ImeSubtypeListItem> sortedEnabledItems) { + final var switchingAwareImeSubtypes = filterImeSubtypeList(sortedEnabledItems, + true /* supportsSwitchingToNextInputMethod */); + final var switchingUnawareImeSubtypes = filterImeSubtypeList(sortedEnabledItems, + false /* supportsSwitchingToNextInputMethod */); + + final DynamicRotationList switchingAwareRotationList; + if (currentInstance != null && Objects.equals( + currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, + switchingAwareImeSubtypes)) { + // Can reuse the current instance. + switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; + } else { + switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); } - StaticRotationList switchingUnawareRotationList = null; - { - final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList( - sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */); - if (currentInstance != null - && currentInstance.mSwitchingUnawareRotationList != null - && Objects.equals( - currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, - switchingUnawareImeSubtypes)) { - // Can reuse the current instance. - switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; - } - if (switchingUnawareRotationList == null) { - switchingUnawareRotationList = - new StaticRotationList(switchingUnawareImeSubtypes); - } + final StaticRotationList switchingUnawareRotationList; + if (currentInstance != null && Objects.equals( + currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, + switchingUnawareImeSubtypes)) { + // Can reuse the current instance. + switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; + } else { + switchingUnawareRotationList = new StaticRotationList(switchingUnawareImeSubtypes); } return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList); } - private ControllerImpl(final DynamicRotationList switchingAwareRotationList, - final StaticRotationList switchingUnawareRotationList) { + private ControllerImpl(@NonNull DynamicRotationList switchingAwareRotationList, + @NonNull StaticRotationList switchingUnawareRotationList) { mSwitchingAwareRotationList = switchingAwareRotationList; mSwitchingUnawareRotationList = switchingUnawareRotationList; } - public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, - InputMethodSubtype subtype) { + @Nullable + public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, + @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype) { if (imi == null) { return null; } @@ -441,18 +447,17 @@ final class InputMethodSubtypeSwitchingController { } } - public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { - if (imi == null) { - return; - } + public void onUserActionLocked(@NonNull InputMethodInfo imi, + @Nullable InputMethodSubtype subtype) { if (imi.supportsSwitchingToNextInputMethod()) { mSwitchingAwareRotationList.onUserAction(imi, subtype); } } + @NonNull private static List<ImeSubtypeListItem> filterImeSubtypeList( - final List<ImeSubtypeListItem> items, - final boolean supportsSwitchingToNextInputMethod) { + @NonNull List<ImeSubtypeListItem> items, + boolean supportsSwitchingToNextInputMethod) { final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); final int numItems = items.size(); for (int i = 0; i < numItems; i++) { @@ -473,26 +478,21 @@ final class InputMethodSubtypeSwitchingController { } } + @NonNull private final Context mContext; @UserIdInt private final int mUserId; + @NonNull private ControllerImpl mController; - private InputMethodSubtypeSwitchingController(@NonNull Context context, - @NonNull InputMethodMap methodMap, @UserIdInt int userId) { + InputMethodSubtypeSwitchingController(@NonNull Context context, + @NonNull InputMethodSettings settings) { mContext = context; - mUserId = userId; + mUserId = settings.getUserId(); mController = ControllerImpl.createFrom(null, getSortedInputMethodAndSubtypeList( false /* includeAuxiliarySubtypes */, false /* isScreenLocked */, - false /* forImeMenu */, context, methodMap, userId)); - } - - @NonNull - public static InputMethodSubtypeSwitchingController createInstanceLocked( - @NonNull Context context, - @NonNull InputMethodMap methodMap, @UserIdInt int userId) { - return new InputMethodSubtypeSwitchingController(context, methodMap, userId); + false /* forImeMenu */, context, settings)); } @AnyThread @@ -501,39 +501,25 @@ final class InputMethodSubtypeSwitchingController { return mUserId; } - public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { - if (mController == null) { - if (DEBUG) { - Slog.e(TAG, "mController shouldn't be null."); - } - return; - } + public void onUserActionLocked(@NonNull InputMethodInfo imi, + @Nullable InputMethodSubtype subtype) { mController.onUserActionLocked(imi, subtype); } - public void resetCircularListLocked(@NonNull InputMethodMap methodMap) { + public void resetCircularListLocked(@NonNull InputMethodSettings settings) { mController = ControllerImpl.createFrom(mController, getSortedInputMethodAndSubtypeList( false /* includeAuxiliarySubtypes */, false /* isScreenLocked */, - false /* forImeMenu */, mContext, methodMap, mUserId)); + false /* forImeMenu */, mContext, settings)); } - public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, - InputMethodSubtype subtype) { - if (mController == null) { - if (DEBUG) { - Slog.e(TAG, "mController shouldn't be null."); - } - return null; - } + @Nullable + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype) { return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); } public void dump(@NonNull Printer pw, @NonNull String prefix) { - if (mController != null) { - mController.dump(pw, prefix); - } else { - pw.println(prefix + "mController=null"); - } + mController.dump(pw, prefix); } } diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java index 559b6252fd07..4764e4fadd6e 100644 --- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java +++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java @@ -44,6 +44,32 @@ final class SecureSettingsWrapper { @Nullable private static volatile ContentResolver sContentResolver = null; + private static volatile boolean sTestMode = false; + + /** + * Can be called from unit tests to start the test mode, where a fake implementation will be + * used instead. + * + * <p>The fake implementation is just an {@link ArrayMap}. By default it is empty, and the data + * written can be read back later.</p> + */ + @AnyThread + static void startTestMode() { + sTestMode = true; + } + + /** + * Can be called from unit tests to end the test mode, where a fake implementation will be used + * instead. + */ + @AnyThread + static void endTestMode() { + synchronized (sUserMap) { + sUserMap.clear(); + } + sTestMode = false; + } + /** * Not intended to be instantiated. */ @@ -78,6 +104,52 @@ final class SecureSettingsWrapper { int getInt(String key, int defaultValue); } + private static class FakeReaderWriterImpl implements ReaderWriter { + @GuardedBy("mNonPersistentKeyValues") + private final ArrayMap<String, String> mNonPersistentKeyValues = new ArrayMap<>(); + + @AnyThread + @Override + public void putString(String key, String value) { + synchronized (mNonPersistentKeyValues) { + mNonPersistentKeyValues.put(key, value); + } + } + + @AnyThread + @Nullable + @Override + public String getString(String key, String defaultValue) { + synchronized (mNonPersistentKeyValues) { + if (mNonPersistentKeyValues.containsKey(key)) { + final String result = mNonPersistentKeyValues.get(key); + return result != null ? result : defaultValue; + } + return defaultValue; + } + } + + @AnyThread + @Override + public void putInt(String key, int value) { + synchronized (mNonPersistentKeyValues) { + mNonPersistentKeyValues.put(key, String.valueOf(value)); + } + } + + @AnyThread + @Override + public int getInt(String key, int defaultValue) { + synchronized (mNonPersistentKeyValues) { + if (mNonPersistentKeyValues.containsKey(key)) { + final String result = mNonPersistentKeyValues.get(key); + return result != null ? Integer.parseInt(result) : defaultValue; + } + return defaultValue; + } + } + } + private static class UnlockedUserImpl implements ReaderWriter { @UserIdInt private final int mUserId; @@ -200,6 +272,9 @@ final class SecureSettingsWrapper { private static ReaderWriter createImpl(@NonNull UserManagerInternal userManagerInternal, @UserIdInt int userId) { + if (sTestMode) { + return new FakeReaderWriterImpl(); + } return userManagerInternal.isUserUnlockingOrUnlocked(userId) ? new UnlockedUserImpl(userId, sContentResolver) : new LockedUserImpl(userId, sContentResolver); @@ -234,6 +309,9 @@ final class SecureSettingsWrapper { return readerWriter; } } + if (sTestMode) { + return putOrGet(userId, new FakeReaderWriterImpl()); + } final UserManagerInternal userManagerInternal = LocalServices.getService(UserManagerInternal.class); if (!userManagerInternal.exists(userId)) { @@ -276,6 +354,10 @@ final class SecureSettingsWrapper { */ @AnyThread static void onUserStarting(@UserIdInt int userId) { + if (sTestMode) { + putOrGet(userId, new FakeReaderWriterImpl()); + return; + } putOrGet(userId, createImpl(LocalServices.getService(UserManagerInternal.class), userId)); } @@ -286,6 +368,10 @@ final class SecureSettingsWrapper { */ @AnyThread static void onUserUnlocking(@UserIdInt int userId) { + if (sTestMode) { + putOrGet(userId, new FakeReaderWriterImpl()); + return; + } final ReaderWriter readerWriter = new UnlockedUserImpl(userId, sContentResolver); putOrGet(userId, readerWriter); } diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java index 2b19d3e0a2ea..5da4e89c88ee 100644 --- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java +++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java @@ -88,6 +88,9 @@ final class UserDataRepository { @NonNull final InputMethodBindingController mBindingController; + @NonNull + final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController; + /** * Intended to be instantiated only from this file. */ @@ -95,6 +98,8 @@ final class UserDataRepository { @NonNull InputMethodBindingController bindingController) { mUserId = userId; mBindingController = bindingController; + mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( + InputMethodSettings.createEmptyMap(userId)); } @Override diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index 363a4a7be3db..91a4d6f1707f 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -1126,7 +1126,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } } - private void sendHostEndpointConnectedEvent() { + void sendHostEndpointConnectedEvent() { HostEndpointInfo info = new HostEndpointInfo(); info.hostEndpointId = (char) mHostEndPointId; info.packageName = mPackage; diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index b3fb147a318b..7a722bc914f7 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -74,6 +74,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -158,10 +159,8 @@ public class ContextHubService extends IContextHubService.Stub { // A queue of reliable message records for duplicate detection private final PriorityQueue<ReliableMessageRecord> mReliableMessageRecordQueue = - new PriorityQueue<ReliableMessageRecord>( - (ReliableMessageRecord left, ReliableMessageRecord right) -> { - return Long.compare(left.getTimestamp(), right.getTimestamp()); - }); + new PriorityQueue<>( + Comparator.comparingLong(ReliableMessageRecord::getTimestamp)); // The test mode manager that manages behaviors during test mode. private final TestModeManager mTestModeManager = new TestModeManager(); @@ -179,10 +178,10 @@ public class ContextHubService extends IContextHubService.Stub { private boolean mIsBtMainEnabled = false; // True if test mode is enabled for the Context Hub - private AtomicBoolean mIsTestModeEnabled = new AtomicBoolean(false); + private final AtomicBoolean mIsTestModeEnabled = new AtomicBoolean(false); // A hashmap used to record if a contexthub is waiting for daily query - private Set<Integer> mMetricQueryPendingContextHubIds = + private final Set<Integer> mMetricQueryPendingContextHubIds = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>()); // Lock object for sendWifiSettingUpdate() @@ -242,10 +241,14 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void handleServiceRestart() { - Log.i(TAG, "Starting Context Hub Service restart"); + Log.i(TAG, "Recovering from Context Hub HAL restart..."); initExistingCallbacks(); resetSettings(); - Log.i(TAG, "Finished Context Hub Service restart"); + if (Flags.reconnectHostEndpointsAfterHalRestart()) { + mClientManager.forEachClientOfHub(mContextHubId, + ContextHubClientBroker::sendHostEndpointConnectedEvent); + } + Log.i(TAG, "Finished recovering from Context Hub HAL restart"); } @Override @@ -317,11 +320,11 @@ public class ContextHubService extends IContextHubService.Stub { */ private static final int MAX_PROBABILITY_PERCENT = 100; - private Random mRandom = new Random(); + private final Random mRandom = new Random(); /** - * @see ContextHubServiceCallback.handleNanoappMessage * @return whether the message was handled + * @see ContextHubServiceCallback#handleNanoappMessage */ public boolean handleNanoappMessage(int contextHubId, short hostEndpointId, NanoAppMessage message, @@ -331,7 +334,8 @@ public class ContextHubService extends IContextHubService.Stub { } if (Flags.reliableMessageDuplicateDetectionService() - && didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) { + && mRandom.nextInt(MAX_PROBABILITY_PERCENT) + < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) { Log.i(TAG, "[TEST MODE] Duplicating message (" + NUM_MESSAGES_TO_DUPLICATE + " sends) with message sequence number: " @@ -344,16 +348,6 @@ public class ContextHubService extends IContextHubService.Stub { } return false; } - - /** - * Returns true if the event with percentPercent did happen. - * - * @param probabilityPercent the percent probability of the event. - * @return true if the event happened, false otherwise. - */ - private boolean didEventHappen(int probabilityPercent) { - return mRandom.nextInt(MAX_PROBABILITY_PERCENT) < probabilityPercent; - } } public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) { @@ -476,7 +470,7 @@ public class ContextHubService extends IContextHubService.Stub { hubInfo = mContextHubWrapper.getHubs(); } catch (RemoteException e) { Log.e(TAG, "RemoteException while getting Context Hub info", e); - hubInfo = new Pair(Collections.emptyList(), Collections.emptyList()); + hubInfo = new Pair<>(Collections.emptyList(), Collections.emptyList()); } long bootTimeNs = SystemClock.elapsedRealtimeNanos() - startTimeNs; @@ -536,6 +530,7 @@ public class ContextHubService extends IContextHubService.Stub { for (int contextHubId : mContextHubIdToInfoMap.keySet()) { try { mContextHubWrapper.registerExistingCallback(contextHubId); + Log.i(TAG, "Re-registered callback to context hub " + contextHubId); } catch (RemoteException e) { Log.e(TAG, "RemoteException while registering existing service callback for hub " + "(ID = " + contextHubId + ")", e); @@ -647,7 +642,7 @@ public class ContextHubService extends IContextHubService.Stub { mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers( SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> { // If we are in HSUM mode, any user can change the microphone setting - if (mUserManager.isHeadlessSystemUserMode() || userId == getCurrentUserId()) { + if (UserManager.isHeadlessSystemUserMode() || userId == getCurrentUserId()) { Log.d(TAG, "User: " + userId + " mic privacy: " + enabled); sendMicrophoneDisableSettingUpdate(enabled); } @@ -720,33 +715,30 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override - public int[] getContextHubHandles() throws RemoteException { + public int[] getContextHubHandles() { super.getContextHubHandles_enforcePermission(); - return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet()); } @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override - public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException { + public ContextHubInfo getContextHubInfo(int contextHubHandle) { super.getContextHubInfo_enforcePermission(); - if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) { Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo"); return null; } - return mContextHubIdToInfoMap.get(contextHubHandle); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Returns a List of ContextHubInfo object describing the available hubs. * * @return the List of ContextHubInfo objects */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override - public List<ContextHubInfo> getContextHubs() throws RemoteException { + public List<ContextHubInfo> getContextHubs() { super.getContextHubs_enforcePermission(); return mContextHubInfoList; @@ -814,7 +806,7 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override - public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException { + public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) { super.loadNanoApp_enforcePermission(); if (mContextHubWrapper == null) { @@ -843,7 +835,7 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override - public int unloadNanoApp(int nanoAppHandle) throws RemoteException { + public int unloadNanoApp(int nanoAppHandle) { super.unloadNanoApp_enforcePermission(); if (mContextHubWrapper == null) { @@ -870,7 +862,7 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override - public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) throws RemoteException { + public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) { super.getNanoAppInstanceInfo_enforcePermission(); @@ -880,7 +872,7 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public int[] findNanoAppOnHub( - int contextHubHandle, NanoAppFilter filter) throws RemoteException { + int contextHubHandle, NanoAppFilter filter) { super.findNanoAppOnHub_enforcePermission(); @@ -895,20 +887,19 @@ public class ContextHubService extends IContextHubService.Stub { int[] retArray = new int[foundInstances.size()]; for (int i = 0; i < foundInstances.size(); i++) { - retArray[i] = foundInstances.get(i).intValue(); + retArray[i] = foundInstances.get(i); } return retArray; } /** * Performs a query at the specified hub. - * <p> - * This method should only be invoked internally by the service, either to update the service + * + * <p>This method should only be invoked internally by the service, either to update the service * cache or as a result of an explicit query requested by a client through the sendMessage API. * * @param contextHubId the ID of the hub to do the query * @return true if the query succeeded - * @throws IllegalStateException if the transaction queue is full */ private boolean queryNanoAppsInternal(int contextHubId) { if (mContextHubWrapper == null) { @@ -1003,7 +994,7 @@ public class ContextHubService extends IContextHubService.Stub { return; } - byte errorCode = ErrorCode.OK; + byte errorCode; synchronized (mReliableMessageRecordQueue) { Optional<ReliableMessageRecord> record = findReliableMessageRecord(contextHubId, @@ -1219,7 +1210,6 @@ public class ContextHubService extends IContextHubService.Stub { return mContextHubIdToInfoMap.containsKey(contextHubId); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Creates and registers a client at the service for the specified Context Hub. * @@ -1232,10 +1222,11 @@ public class ContextHubService extends IContextHubService.Stub { * @throws IllegalStateException if max number of clients have already registered * @throws NullPointerException if clientCallback is null */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public IContextHubClient createClient( int contextHubId, IContextHubClientCallback clientCallback, - @Nullable String attributionTag, String packageName) throws RemoteException { + @Nullable String attributionTag, String packageName) { super.createClient_enforcePermission(); if (!isValidContextHubId(contextHubId)) { @@ -1250,7 +1241,6 @@ public class ContextHubService extends IContextHubService.Stub { contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Creates and registers a PendingIntent client at the service for the specified Context Hub. * @@ -1262,10 +1252,11 @@ public class ContextHubService extends IContextHubService.Stub { * @throws IllegalArgumentException if hubInfo does not represent a valid hub * @throws IllegalStateException if there were too many registered clients at the service */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public IContextHubClient createPendingIntentClient( int contextHubId, PendingIntent pendingIntent, long nanoAppId, - @Nullable String attributionTag) throws RemoteException { + @Nullable String attributionTag) { super.createPendingIntentClient_enforcePermission(); if (!isValidContextHubId(contextHubId)) { @@ -1277,15 +1268,14 @@ public class ContextHubService extends IContextHubService.Stub { contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Loads a nanoapp binary at the specified Context hub. * * @param contextHubId the ID of the hub to load the binary * @param transactionCallback the client-facing transaction callback interface * @param nanoAppBinary the binary to load - * @throws IllegalStateException if the transaction queue is full */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public void loadNanoAppOnHub( int contextHubId, IContextHubTransactionCallback transactionCallback, @@ -1308,15 +1298,14 @@ public class ContextHubService extends IContextHubService.Stub { mTransactionManager.addTransaction(transaction); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Unloads a nanoapp from the specified Context Hub. * * @param contextHubId the ID of the hub to unload the nanoapp * @param transactionCallback the client-facing transaction callback interface * @param nanoAppId the ID of the nanoapp to unload - * @throws IllegalStateException if the transaction queue is full */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public void unloadNanoAppFromHub( int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId) @@ -1333,19 +1322,17 @@ public class ContextHubService extends IContextHubService.Stub { mTransactionManager.addTransaction(transaction); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Enables a nanoapp at the specified Context Hub. * * @param contextHubId the ID of the hub to enable the nanoapp * @param transactionCallback the client-facing transaction callback interface * @param nanoAppId the ID of the nanoapp to enable - * @throws IllegalStateException if the transaction queue is full */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public void enableNanoApp( - int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId) - throws RemoteException { + int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId) { super.enableNanoApp_enforcePermission(); if (!checkHalProxyAndContextHubId( @@ -1358,19 +1345,17 @@ public class ContextHubService extends IContextHubService.Stub { mTransactionManager.addTransaction(transaction); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Disables a nanoapp at the specified Context Hub. * * @param contextHubId the ID of the hub to disable the nanoapp * @param transactionCallback the client-facing transaction callback interface * @param nanoAppId the ID of the nanoapp to disable - * @throws IllegalStateException if the transaction queue is full */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public void disableNanoApp( - int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId) - throws RemoteException { + int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId) { super.disableNanoApp_enforcePermission(); if (!checkHalProxyAndContextHubId( @@ -1383,17 +1368,16 @@ public class ContextHubService extends IContextHubService.Stub { mTransactionManager.addTransaction(transaction); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Queries for a list of nanoapps from the specified Context hub. * * @param contextHubId the ID of the hub to query * @param transactionCallback the client-facing transaction callback interface - * @throws IllegalStateException if the transaction queue is full */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override - public void queryNanoApps(int contextHubId, IContextHubTransactionCallback transactionCallback) - throws RemoteException { + public void queryNanoApps(int contextHubId, + IContextHubTransactionCallback transactionCallback) { super.queryNanoApps_enforcePermission(); if (!checkHalProxyAndContextHubId( @@ -1406,16 +1390,15 @@ public class ContextHubService extends IContextHubService.Stub { mTransactionManager.addTransaction(transaction); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Queries for a list of preloaded nanoapp IDs from the specified Context Hub. * * @param hubInfo The Context Hub to query a list of nanoapps from. * @return The list of 64-bit IDs of the preloaded nanoapps. - * @throws NullPointerException if hubInfo is null */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override - public long[] getPreloadedNanoAppIds(ContextHubInfo hubInfo) throws RemoteException { + public long[] getPreloadedNanoAppIds(ContextHubInfo hubInfo) { super.getPreloadedNanoAppIds_enforcePermission(); Objects.requireNonNull(hubInfo, "hubInfo cannot be null"); @@ -1426,7 +1409,6 @@ public class ContextHubService extends IContextHubService.Stub { return nanoappIds; } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) /** * Puts the context hub in and out of test mode. Test mode is a clean state * where tests can be executed in the same environment. If enable is true, @@ -1442,6 +1424,7 @@ public class ContextHubService extends IContextHubService.Stub { * test mode. * @return If true, the operation was successful; false otherwise. */ + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public boolean setTestMode(boolean enable) { super.setTestMode_enforcePermission(); @@ -1551,10 +1534,6 @@ public class ContextHubService extends IContextHubService.Stub { } } - private void checkPermissions() { - ContextHubServiceUtil.checkPermissions(mContext); - } - private int onMessageReceiptOldApi( int msgType, int contextHubHandle, int appInstance, byte[] data) { if (data == null) { @@ -1586,7 +1565,6 @@ public class ContextHubService extends IContextHubService.Stub { callback.onMessageReceipt(contextHubHandle, appInstance, msg); } catch (RemoteException e) { Log.i(TAG, "Exception (" + e + ") calling remote callback (" + callback + ")."); - continue; } } mCallbacksList.finishBroadcast(); @@ -1729,8 +1707,8 @@ public class ContextHubService extends IContextHubService.Stub { * Hub. */ private void sendMicrophoneDisableSettingUpdateForCurrentUser() { - boolean isEnabled = mSensorPrivacyManagerInternal == null ? false : - mSensorPrivacyManagerInternal.isSensorPrivacyEnabled( + boolean isEnabled = mSensorPrivacyManagerInternal != null + && mSensorPrivacyManagerInternal.isSensorPrivacyEnabled( getCurrentUserId(), SensorPrivacyManager.Sensors.MICROPHONE); sendMicrophoneDisableSettingUpdate(isEnabled); } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 3673eb027096..56b93e8ded82 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -839,6 +839,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { + "Disallowed route: " + route); } + + if (route.isSystemRouteType()) { + throw new SecurityException( + "Only the system is allowed to publish routes with system route types. " + + "Disallowed route: " + + route); + } } Connection connection = mConnectionRef.get(); diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java index efca5986e35a..c3ace15a8c0f 100644 --- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java +++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java @@ -19,13 +19,11 @@ package com.android.server.notification; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.service.notification.Condition; -import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.text.format.DateUtils; import android.util.Log; @@ -41,9 +39,6 @@ public class CountdownConditionProvider extends SystemConditionProviderService { private static final String TAG = "ConditionProviders.CCP"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); - public static final ComponentName COMPONENT = - new ComponentName("android", CountdownConditionProvider.class.getName()); - private static final String ACTION = CountdownConditionProvider.class.getName(); private static final int REQUEST_CODE = 100; private static final String EXTRA_CONDITION_ID = "condition_id"; @@ -60,31 +55,16 @@ public class CountdownConditionProvider extends SystemConditionProviderService { } @Override - public ComponentName getComponent() { - return COMPONENT; - } - - @Override public boolean isValidConditionId(Uri id) { return ZenModeConfig.isValidCountdownConditionId(id); } @Override - public void attachBase(Context base) { - attachBaseContext(base); - } - - @Override public void onBootComplete() { // noop } @Override - public IConditionProvider asInterface() { - return (IConditionProvider) onBind(null); - } - - @Override public void dump(PrintWriter pw, DumpFilter filter) { pw.println(" CountdownConditionProvider:"); pw.print(" mConnected="); pw.println(mConnected); diff --git a/services/core/java/com/android/server/notification/CustomManualConditionProvider.java b/services/core/java/com/android/server/notification/CustomManualConditionProvider.java new file mode 100644 index 000000000000..9531c5e58851 --- /dev/null +++ b/services/core/java/com/android/server/notification/CustomManualConditionProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.notification; + +import android.net.Uri; +import android.service.notification.ZenModeConfig; + +import java.io.PrintWriter; + +/** + * Condition provider used for custom manual rules (i.e. user-created rules without an automatic + * trigger). + */ +public class CustomManualConditionProvider extends SystemConditionProviderService { + + private static final String SIMPLE_NAME = CustomManualConditionProvider.class.getSimpleName(); + + @Override + public boolean isValidConditionId(Uri id) { + return ZenModeConfig.isValidCustomManualConditionId(id); + } + + @Override + public void onBootComplete() { + // Nothing to do. + } + + @Override + public void onConnected() { + // No need to keep subscriptions because we won't ever call notifyConditions + } + + @Override + public void onSubscribe(Uri conditionId) { + // No need to keep subscriptions because we won't ever call notifyConditions + } + + @Override + public void onUnsubscribe(Uri conditionId) { + // No need to keep subscriptions because we won't ever call notifyConditions + } + + @Override + public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) { + pw.print(" "); pw.print(SIMPLE_NAME); pw.println(": ENABLED"); + } +} diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java index 4fe7a27c64c6..00dd547f6c4e 100644 --- a/services/core/java/com/android/server/notification/EventConditionProvider.java +++ b/services/core/java/com/android/server/notification/EventConditionProvider.java @@ -19,7 +19,6 @@ package com.android.server.notification; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -31,7 +30,6 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.service.notification.Condition; -import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.EventInfo; import android.util.ArraySet; @@ -54,8 +52,6 @@ public class EventConditionProvider extends SystemConditionProviderService { private static final String TAG = "ConditionProviders.ECP"; private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG); - public static final ComponentName COMPONENT = - new ComponentName("android", EventConditionProvider.class.getName()); private static final String NOT_SHOWN = "..."; private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName(); private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE"; @@ -82,11 +78,6 @@ public class EventConditionProvider extends SystemConditionProviderService { } @Override - public ComponentName getComponent() { - return COMPONENT; - } - - @Override public boolean isValidConditionId(Uri id) { return ZenModeConfig.isValidEventConditionId(id); } @@ -166,16 +157,6 @@ public class EventConditionProvider extends SystemConditionProviderService { } } - @Override - public void attachBase(Context base) { - attachBaseContext(base); - } - - @Override - public IConditionProvider asInterface() { - return (IConditionProvider) onBind(null); - } - private void reloadTrackers() { if (DEBUG) Slog.d(TAG, "reloadTrackers"); for (int i = 0; i < mTrackers.size(); i++) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b436c8b42edf..a4f534eeba67 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -46,6 +46,10 @@ import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; +import static android.app.NotificationChannel.NEWS_ID; +import static android.app.NotificationChannel.PROMOTIONS_ID; +import static android.app.NotificationChannel.RECS_ID; +import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; import static android.app.NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED; @@ -98,6 +102,11 @@ import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROU import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_NULL; import static android.os.UserHandle.USER_SYSTEM; +import static android.service.notification.Adjustment.KEY_TYPE; +import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION; +import static android.service.notification.Adjustment.TYPE_NEWS; +import static android.service.notification.Adjustment.TYPE_PROMOTION; +import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA; import static android.service.notification.Flags.callstyleCallbackApi; import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners; import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle; @@ -458,7 +467,8 @@ public class NotificationManagerService extends SystemService { Adjustment.KEY_IMPORTANCE_PROPOSAL, Adjustment.KEY_SENSITIVE_CONTENT, Adjustment.KEY_RANKING_SCORE, - Adjustment.KEY_NOT_CONVERSATION + Adjustment.KEY_NOT_CONVERSATION, + Adjustment.KEY_TYPE }; static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] { @@ -1023,7 +1033,7 @@ public class NotificationManagerService extends SystemService { summary.getSbn().getNotification().color = summaryAttr.iconColor; summary.getSbn().getNotification().visibility = summaryAttr.visibility; mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground, - mPostNotificationTrackerFactory.newTracker(null))); + /* isAppProvided= */ false, mPostNotificationTrackerFactory.newTracker(null))); } } @@ -1650,7 +1660,7 @@ public class NotificationManagerService extends SystemService { // Force isAppForeground true here, because for sysui's purposes we // want to adjust the flag behaviour. mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), - r, true /* isAppForeground*/, + r, /* isAppForeground= */ true , /* isAppProvided= */ false, mPostNotificationTrackerFactory.newTracker(null))); } } @@ -1681,7 +1691,7 @@ public class NotificationManagerService extends SystemService { // want to be able to adjust the flag behaviour. mHandler.post( new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, - /* foreground= */ true, + /* foreground= */ true, /* isAppProvided= */ false, mPostNotificationTrackerFactory.newTracker(null))); } } @@ -2706,7 +2716,7 @@ public class NotificationManagerService extends SystemService { enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(), r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn, - false /* byForegroundService */); + /* byForegroundService= */ false, /* isAppProvided= */ false); } catch (Exception e) { Slog.e(TAG, "Cannot un-snooze notification", e); } @@ -2851,6 +2861,7 @@ public class NotificationManagerService extends SystemService { final boolean isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, + /* isAppProvided= */ false, mPostNotificationTrackerFactory.newTracker(null))); } } @@ -3763,7 +3774,7 @@ public class NotificationManagerService extends SystemService { Notification notification, int userId) throws RemoteException { enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(), tag, id, notification, userId, - false /* byForegroundService */); + /* byForegroundService= */ false, /* isAppProvided= */ true); } @Override @@ -6608,6 +6619,30 @@ public class NotificationManagerService extends SystemService { for (String removeKey : toRemove) { adjustments.remove(removeKey); } + if (android.service.notification.Flags.notificationClassification() + && adjustments.containsKey(KEY_TYPE)) { + NotificationChannel newChannel = null; + int type = adjustments.getInt(KEY_TYPE); + if (TYPE_NEWS == type) { + newChannel = mPreferencesHelper.getNotificationChannel( + r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false); + } else if (TYPE_PROMOTION == type) { + newChannel = mPreferencesHelper.getNotificationChannel( + r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false); + } else if (TYPE_SOCIAL_MEDIA == type) { + newChannel = mPreferencesHelper.getNotificationChannel( + r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false); + } else if (TYPE_CONTENT_RECOMMENDATION == type) { + newChannel = mPreferencesHelper.getNotificationChannel( + r.getSbn().getPackageName(), r.getUid(), RECS_ID, false); + } + if (newChannel == null) { + adjustments.remove(KEY_TYPE); + } else { + // swap app provided type with the real thing + adjustments.putParcelable(KEY_TYPE, newChannel); + } + } r.addAdjustment(adjustment); if (adjustment.getSignals().containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) { logSensitiveAdjustmentReceived(isPosted, @@ -7025,7 +7060,7 @@ public class NotificationManagerService extends SystemService { public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid, String tag, int id, Notification notification, int userId) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - userId, false /* byForegroundService */); + userId, /* byForegroundService= */ false , /* isAppProvided= */ true); } @Override @@ -7033,7 +7068,7 @@ public class NotificationManagerService extends SystemService { String tag, int id, Notification notification, int userId, boolean byForegroundService) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - userId, byForegroundService); + userId, byForegroundService, /* isAppProvided= */ true); } @Override @@ -7245,7 +7280,8 @@ public class NotificationManagerService extends SystemService { r.getSbn().getInitialPid(), r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId(), /* postSilently= */ true, - /* byForegroundService= */ false); + /* byForegroundService= */ false, + /* isAppProvided= */ false); } int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) { @@ -7306,19 +7342,21 @@ public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, - int incomingUserId, boolean byForegroundService) { + int incomingUserId, boolean byForegroundService, boolean isAppProvided) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - incomingUserId, false /* postSilently */, byForegroundService); + incomingUserId, false /* postSilently */, byForegroundService, isAppProvided); } void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, - int incomingUserId, boolean postSilently, boolean byForegroundService) { + int incomingUserId, boolean postSilently, boolean byForegroundService, + boolean isAppProvided) { PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid); boolean enqueued = false; try { enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, - notification, incomingUserId, postSilently, tracker, byForegroundService); + notification, incomingUserId, postSilently, tracker, byForegroundService, + isAppProvided); } finally { if (!enqueued) { tracker.cancel(); @@ -7344,7 +7382,7 @@ public class NotificationManagerService extends SystemService { private boolean enqueueNotificationInternal(final String pkg, final String opPkg, //HUI final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently, - PostNotificationTracker tracker, boolean byForegroundService) { + PostNotificationTracker tracker, boolean byForegroundService, boolean isAppProvided) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); @@ -7545,7 +7583,8 @@ public class NotificationManagerService extends SystemService { // Need escalated privileges to get package importance. final int packageImportance = getPackageImportanceWithIdentity(pkg); boolean isAppForeground = packageImportance == IMPORTANCE_FOREGROUND; - mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, tracker)); + mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, + /* isAppProvided= */ isAppProvided, tracker)); return true; } @@ -7925,6 +7964,7 @@ public class NotificationManagerService extends SystemService { mHandler.post( new EnqueueNotificationRunnable( r.getUser().getIdentifier(), r, isAppForeground, + /* isAppProvided= */ false, mPostNotificationTrackerFactory.newTracker(null))); } } @@ -8495,13 +8535,15 @@ public class NotificationManagerService extends SystemService { private final NotificationRecord r; private final int userId; private final boolean isAppForeground; + private final boolean isAppProvided; private final PostNotificationTracker mTracker; EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground, - PostNotificationTracker tracker) { + boolean isAppProvided, PostNotificationTracker tracker) { this.userId = userId; this.r = r; this.isAppForeground = foreground; + this.isAppProvided = isAppProvided; this.mTracker = checkNotNull(tracker); } @@ -8523,6 +8565,13 @@ public class NotificationManagerService extends SystemService { */ private boolean enqueueNotification() { synchronized (mNotificationLock) { + if (android.app.Flags.secureAllowlistToken()) { + // allowlistToken is populated by unparceling, so it will be absent if the + // EnqueueNotificationRunnable is created directly by NMS (as we do for group + // summaries) instead of via notify(). Fix that. + r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN); + } + final long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), @@ -8586,9 +8635,10 @@ public class NotificationManagerService extends SystemService { if (old != null) { enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE; } + int appProvided = isAppProvided ? 1 : 0; EventLogTags.writeNotificationEnqueue(callingUid, callingPid, pkg, id, tag, userId, notification.toString(), - enqueueStatus); + enqueueStatus, appProvided); } // tell the assistant service about the notification @@ -8748,7 +8798,7 @@ public class NotificationManagerService extends SystemService { // was not autogrouped onPost, to avoid an unnecessary sort. // We add the autogroup key to the notification without a // sort here, and it'll be sorted below with extractSignals. - addAutogroupKeyLocked(key, /*requestSort=*/false); + addAutogroupKeyLocked(key, /* requestSort= */false); } } } @@ -11373,7 +11423,7 @@ public class NotificationManagerService extends SystemService { record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(), - record, isAppForeground, + record, isAppForeground, /* isAppProvided= */ false, mPostNotificationTrackerFactory.newTracker(null))); } } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 0c6a6c84b69b..0d4bdf663679 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -710,7 +710,8 @@ public final class NotificationRecord { if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) { final ArrayList<SnoozeCriterion> snoozeCriterionList = adjustment.getSignals().getParcelableArrayList( - Adjustment.KEY_SNOOZE_CRITERIA, android.service.notification.SnoozeCriterion.class); + Adjustment.KEY_SNOOZE_CRITERIA, + android.service.notification.SnoozeCriterion.class); setSnoozeCriteria(snoozeCriterionList); EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_SNOOZE_CRITERIA, snoozeCriterionList.toString()); @@ -736,7 +737,8 @@ public final class NotificationRecord { } if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) { setSystemGeneratedSmartActions( - signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, android.app.Notification.Action.class)); + signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, + android.app.Notification.Action.class)); EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_CONTEXTUAL_ACTIONS, getSystemGeneratedSmartActions().toString()); @@ -778,6 +780,14 @@ public final class NotificationRecord { Adjustment.KEY_SENSITIVE_CONTENT, Boolean.toString(mSensitiveContent)); } + if (android.service.notification.Flags.notificationClassification() + && signals.containsKey(Adjustment.KEY_TYPE)) { + updateNotificationChannel(signals.getParcelable(Adjustment.KEY_TYPE, + NotificationChannel.class)); + EventLogTags.writeNotificationAdjusted(getKey(), + Adjustment.KEY_TYPE, + mChannel.getId()); + } if (!signals.isEmpty() && adjustment.getIssuer() != null) { mAdjustmentIssuer = adjustment.getIssuer(); } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 309e9450a01d..9ee05d8759b2 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -22,11 +22,14 @@ import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.os.UserHandle.USER_SYSTEM; +import static android.service.notification.Flags.notificationClassification; + import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES; @@ -514,6 +517,10 @@ public class PreferencesHelper implements RankingConfig { Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e); } + if (notificationClassification()) { + addReservedChannelsLocked(r); + } + if (r.uid == UNKNOWN_UID) { if (Flags.persistIncompleteRestoreData()) { r.userId = userId; @@ -603,6 +610,40 @@ public class PreferencesHelper implements RankingConfig { return true; } + private void addReservedChannelsLocked(PackagePreferences p) { + if (!p.channels.containsKey(NotificationChannel.PROMOTIONS_ID)) { + NotificationChannel channel = new NotificationChannel( + NotificationChannel.PROMOTIONS_ID, + mContext.getString(R.string.promotional_notification_channel_label), + IMPORTANCE_LOW); + p.channels.put(channel.getId(), channel); + } + + if (!p.channels.containsKey(NotificationChannel.RECS_ID)) { + NotificationChannel channel = new NotificationChannel( + NotificationChannel.RECS_ID, + mContext.getString(R.string.recs_notification_channel_label), + IMPORTANCE_LOW); + p.channels.put(channel.getId(), channel); + } + + if (!p.channels.containsKey(NotificationChannel.NEWS_ID)) { + NotificationChannel channel = new NotificationChannel( + NotificationChannel.NEWS_ID, + mContext.getString(R.string.news_notification_channel_label), + IMPORTANCE_LOW); + p.channels.put(channel.getId(), channel); + } + + if (!p.channels.containsKey(NotificationChannel.SOCIAL_MEDIA_ID)) { + NotificationChannel channel = new NotificationChannel( + NotificationChannel.SOCIAL_MEDIA_ID, + mContext.getString(R.string.social_notification_channel_label), + IMPORTANCE_LOW); + p.channels.put(channel.getId(), channel); + } + } + public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException { out.startTag(null, TAG_RANKING); out.attributeInt(null, ATT_VERSION, XML_VERSION); @@ -1801,7 +1842,7 @@ public class PreferencesHelper implements RankingConfig { public boolean onlyHasDefaultChannel(String pkg, int uid) { synchronized (mPackagePreferences) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); - if (r.channels.size() == 1 + if (r.channels.size() == (notificationClassification() ? 5 : 1) && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { return true; } @@ -2611,6 +2652,7 @@ public class PreferencesHelper implements RankingConfig { context.getResources().getString( R.string.default_notification_channel_label)); } + // TODO (b/346396459): Localize all reserved channels } } } diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java index 737353dc5151..6efe88f6a155 100644 --- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java +++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java @@ -20,7 +20,6 @@ import android.app.ActivityManager; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -28,7 +27,6 @@ import android.net.Uri; import android.os.Binder; import android.provider.Settings; import android.service.notification.Condition; -import android.service.notification.IConditionProvider; import android.service.notification.ScheduleCalendar; import android.service.notification.ZenModeConfig; import android.text.TextUtils; @@ -54,8 +52,6 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { static final String TAG = "ConditionProviders.SCP"; static final boolean DEBUG = true || Log.isLoggable("ConditionProviders", Log.DEBUG); - public static final ComponentName COMPONENT = - new ComponentName("android", ScheduleConditionProvider.class.getName()); private static final String NOT_SHOWN = "..."; private static final String SIMPLE_NAME = ScheduleConditionProvider.class.getSimpleName(); private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE"; @@ -66,7 +62,8 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { private final Context mContext = this; private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>(); - private ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>(); + @GuardedBy("mSnoozedForAlarm") + private final ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>(); private AlarmManager mAlarmManager; private boolean mConnected; @@ -78,11 +75,6 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } @Override - public ComponentName getComponent() { - return COMPONENT; - } - - @Override public boolean isValidConditionId(Uri id) { return ZenModeConfig.isValidScheduleConditionId(id); } @@ -103,7 +95,10 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { pw.println(mSubscriptions.get(conditionId).toString()); } } - pw.println(" snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozedForAlarm)); + synchronized (mSnoozedForAlarm) { + pw.println( + " snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozedForAlarm)); + } dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now); } @@ -149,16 +144,6 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { evaluateSubscriptions(); } - @Override - public void attachBase(Context base) { - attachBaseContext(base); - } - - @Override - public IConditionProvider asInterface() { - return (IConditionProvider) onBind(null); - } - private void evaluateSubscriptions() { if (mAlarmManager == null) { mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); @@ -299,6 +284,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { } } + @GuardedBy("mSnoozedForAlarm") private void saveSnoozedLocked() { final String setting = TextUtils.join(SEPARATOR, mSnoozedForAlarm); final int currentUser = ActivityManager.getCurrentUser(); diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java index 574f04ce670b..97073b77f1f7 100644 --- a/services/core/java/com/android/server/notification/SystemConditionProviderService.java +++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java @@ -31,12 +31,21 @@ import java.util.Date; public abstract class SystemConditionProviderService extends ConditionProviderService { abstract public void dump(PrintWriter pw, DumpFilter filter); - abstract public void attachBase(Context context); - abstract public IConditionProvider asInterface(); - abstract public ComponentName getComponent(); abstract public boolean isValidConditionId(Uri id); abstract public void onBootComplete(); + final ComponentName getComponent() { + return new ComponentName("android", this.getClass().getName()); + } + + final IConditionProvider asInterface() { + return (IConditionProvider) onBind(null); + } + + final void attachBase(Context context) { + attachBaseContext(context); + } + protected static String ts(long time) { return new Date(time) + " (" + time + ")"; } diff --git a/services/core/java/com/android/server/notification/TimeToLiveHelper.java b/services/core/java/com/android/server/notification/TimeToLiveHelper.java index 2facab74e28e..a4460b23c73d 100644 --- a/services/core/java/com/android/server/notification/TimeToLiveHelper.java +++ b/services/core/java/com/android/server/notification/TimeToLiveHelper.java @@ -54,13 +54,17 @@ public class TimeToLiveHelper { private final AlarmManager mAm; @VisibleForTesting + @GuardedBy("mLock") final TreeSet<Pair<Long, String>> mKeys; + final Object mLock = new Object(); public TimeToLiveHelper(NotificationManagerPrivate nm, Context context) { mContext = context; mNm = nm; mAm = context.getSystemService(AlarmManager.class); - mKeys = new TreeSet<>((left, right) -> Long.compare(left.first, right.first)); + synchronized (mLock) { + mKeys = new TreeSet<>((left, right) -> Long.compare(left.first, right.first)); + } IntentFilter timeoutFilter = new IntentFilter(ACTION); timeoutFilter.addDataScheme(SCHEME_TIMEOUT); @@ -73,7 +77,9 @@ public class TimeToLiveHelper { } void dump(PrintWriter pw, String indent) { - pw.println(indent + "mKeys " + mKeys); + synchronized (mLock) { + pw.println(indent + "mKeys " + mKeys); + } } private @NonNull PendingIntent getAlarmPendingIntent(String nextKey, int flags) { @@ -93,30 +99,35 @@ public class TimeToLiveHelper { @VisibleForTesting void scheduleTimeoutLocked(NotificationRecord record, long currentTime) { - removeMatchingEntry(record.getKey()); - - final long timeoutAfter = currentTime + record.getNotification().getTimeoutAfter(); - if (record.getNotification().getTimeoutAfter() > 0) { - final Long currentEarliestTime = mKeys.isEmpty() ? null : mKeys.first().first; - - // Maybe replace alarm with an earlier one - if (currentEarliestTime == null || timeoutAfter < currentEarliestTime) { - if (currentEarliestTime != null) { - cancelFirstAlarm(); + synchronized (mLock) { + removeMatchingEntry(record.getKey()); + + final long timeoutAfter = currentTime + record.getNotification().getTimeoutAfter(); + if (record.getNotification().getTimeoutAfter() > 0) { + final Long currentEarliestTime = mKeys.isEmpty() ? null : mKeys.first().first; + + // Maybe replace alarm with an earlier one + if (currentEarliestTime == null || timeoutAfter < currentEarliestTime) { + if (currentEarliestTime != null) { + cancelFirstAlarm(); + } + mKeys.add(Pair.create(timeoutAfter, record.getKey())); + maybeScheduleFirstAlarm(); + } else { + mKeys.add(Pair.create(timeoutAfter, record.getKey())); } - mKeys.add(Pair.create(timeoutAfter, record.getKey())); - maybeScheduleFirstAlarm(); - } else { - mKeys.add(Pair.create(timeoutAfter, record.getKey())); } } } @VisibleForTesting void cancelScheduledTimeoutLocked(NotificationRecord record) { - removeMatchingEntry(record.getKey()); + synchronized (mLock) { + removeMatchingEntry(record.getKey()); + } } + @GuardedBy("mLock") private void removeMatchingEntry(String key) { if (!mKeys.isEmpty() && key.equals(mKeys.first().second)) { // cancel the first alarm, remove the first entry, maybe schedule the alarm for the new @@ -139,11 +150,13 @@ public class TimeToLiveHelper { } } + @GuardedBy("mLock") private void cancelFirstAlarm() { final PendingIntent pi = getAlarmPendingIntent(mKeys.first().second, FLAG_CANCEL_CURRENT); mAm.cancel(pi); } + @GuardedBy("mLock") private void maybeScheduleFirstAlarm() { if (!mKeys.isEmpty()) { final PendingIntent piNewFirst = getAlarmPendingIntent(mKeys.first().second, @@ -162,13 +175,17 @@ public class TimeToLiveHelper { return; } if (ACTION.equals(action)) { - Pair<Long, String> earliest = mKeys.first(); - String key = intent.getStringExtra(EXTRA_KEY); - if (!earliest.second.equals(key)) { - Slog.wtf(TAG, "Alarm triggered but wasn't the earliest we were tracking"); + String timeoutKey = null; + synchronized (mLock) { + Pair<Long, String> earliest = mKeys.first(); + String key = intent.getStringExtra(EXTRA_KEY); + if (!earliest.second.equals(key)) { + Slog.wtf(TAG, "Alarm triggered but wasn't the earliest we were tracking"); + } + removeMatchingEntry(key); + timeoutKey = earliest.second; } - removeMatchingEntry(key); - mNm.timeoutNotification(earliest.second); + mNm.timeoutNotification(timeoutKey); } } }; diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index 02b5f973d6b1..3650536ac7f6 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -58,6 +58,9 @@ public class ZenModeConditions implements ConditionProviders.Callback { if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.EVENT_PATH)) { mConditionProviders.addSystemProvider(new EventConditionProvider()); } + if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.CUSTOM_MANUAL_PATH)) { + mConditionProviders.addSystemProvider(new CustomManualConditionProvider()); + } mConditionProviders.setCallback(this); } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 267291c1a970..c078409f468c 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -34,6 +34,7 @@ import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; @@ -1134,6 +1135,26 @@ public class ZenModeHelper { modified = true; } + // Allow updating the CPS backing system rules (e.g. for custom manual -> schedule) + if (Flags.modesUi() + && (origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI || origin == UPDATE_ORIGIN_USER) + && Objects.equals(rule.pkg, SystemZenRules.PACKAGE_ANDROID) + && !Objects.equals(rule.component, azr.getOwner())) { + rule.component = azr.getOwner(); + modified = true; + } + + if (Flags.modesUi()) { + if (!azr.isEnabled() && (isNew || rule.enabled)) { + // Creating a rule as disabled, or disabling a previously enabled rule. + // Record whodunit. + rule.disabledOrigin = origin; + } else if (azr.isEnabled()) { + // Enabling or previously enabled. Clear disabler. + rule.disabledOrigin = UPDATE_ORIGIN_UNKNOWN; + } + } + if (!Objects.equals(rule.conditionId, azr.getConditionId())) { rule.conditionId = azr.getConditionId(); modified = true; diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index 69490a81fc86..5b4f310655cd 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -126,10 +126,12 @@ final class ResolveIntentHelper { userId, resolveForStart, /*allowDynamicSplits*/ true); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - var args = new SaferIntentUtils.IntentArgs(intent, resolvedType, - false /* isReceiver */, resolveForStart, filterCallingUid, callingPid); - args.platformCompat = mPlatformCompat; - SaferIntentUtils.filterNonExportedComponents(args, query); + if (resolveForStart) { + var args = new SaferIntentUtils.IntentArgs(intent, resolvedType, + false /* isReceiver */, true, filterCallingUid, callingPid); + args.platformCompat = mPlatformCompat; + SaferIntentUtils.filterNonExportedComponents(args, query); + } final boolean queryMayBeFiltered = UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index db94d0e8b0ba..7f4a5cb7cf8f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -62,6 +62,8 @@ import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.IStopUserCallback; import android.app.KeyguardManager; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatsManager; import android.app.admin.DevicePolicyEventLogger; @@ -147,6 +149,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsService; import com.android.internal.app.SetScreenLockDialogActivity; import com.android.internal.logging.MetricsLogger; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.RoSystemProperties; import com.android.internal.util.DumpUtils; @@ -1070,6 +1074,8 @@ public class UserManagerService extends IUserManager.Stub { if (isAutoLockingPrivateSpaceOnRestartsEnabled()) { autoLockPrivateSpace(); } + + showHsumNotificationIfNeeded(); } private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() { @@ -4163,6 +4169,48 @@ public class UserManagerService extends IUserManager.Stub { mUpdatingSystemUserMode = true; } + /** + * If the device's actual HSUM status differs from that which is defined by its build + * configuration, warn the user. Ignores HSUM emulated status, since that isn't relevant. + * + * The goal is to inform dogfooders that they need to factory reset the device to align their + * device with its build configuration. + */ + private void showHsumNotificationIfNeeded() { + if (RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER == isHeadlessSystemUserMode()) { + // Actual state does match the configuration. Great! + return; + } + if (Build.isDebuggable() + && !TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) { + // Ignore any device that has been playing around with HSUM emulation. + return; + } + Slogf.w(LOG_TAG, "Posting warning that device's HSUM status doesn't match the build's."); + + final String title = mContext + .getString(R.string.wrong_hsum_configuration_notification_title); + final String message = mContext + .getString(R.string.wrong_hsum_configuration_notification_message); + + final Notification notification = + new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER) + .setSmallIcon(R.drawable.stat_sys_adb) + .setWhen(0) + .setOngoing(true) + .setTicker(title) + .setDefaults(0) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(message) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .build(); + + final NotificationManager notificationManager = + mContext.getSystemService(NotificationManager.class); + notificationManager.notifyAsUser( + null, SystemMessage.NOTE_WRONG_HSUM_STATUS, notification, UserHandle.ALL); + } private ResilientAtomicFile getUserListFile() { File tempBackup = new File(mUserListFile.getParent(), mUserListFile.getName() + ".backup"); @@ -5918,6 +5966,7 @@ public class UserManagerService extends IUserManager.Stub { return userData; } + /** For testing only! Directly, unnaturally removes userId from list of users. */ @VisibleForTesting void removeUserInfo(@UserIdInt int userId) { synchronized (mUsersLock) { 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 28254d0d863e..46e6546bbfca 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -274,7 +274,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { mVirtualDeviceManagerInternal = LocalServices.getService(VirtualDeviceManagerInternal.class); } - return mVirtualDeviceManagerInternal.getPersistentIdForDevice(deviceId); + return mVirtualDeviceManagerInternal == null + ? VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT + : mVirtualDeviceManagerInternal.getPersistentIdForDevice(deviceId); } @Override diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index 6b7f2fa1cf0d..4b4e442b7b84 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -48,6 +48,7 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.vibrator.persistence.VibrationXmlParser; +import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; @@ -743,6 +744,11 @@ public final class ShutdownThread extends Thread { */ @VisibleForTesting // For testing vibrations without shutting down device void playShutdownVibration(Context context) { + if (mInjector.isShutdownVibrationDisabled(context)) { + Log.i(TAG, "Vibration disabled in config"); + return; + } + Vibrator vibrator = mInjector.getVibrator(context); if (!vibrator.hasVibrator()) { return; @@ -920,5 +926,14 @@ public final class ShutdownThread extends Thread { return context.getResources().getString( com.android.internal.R.string.config_defaultShutdownVibrationFile); } + + public boolean isShutdownVibrationDisabled(Context context) { + boolean disabledInConfig = context.getResources().getBoolean( + com.android.internal.R.bool.config_disableShutdownVibrationInZen); + boolean isZenMode = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF) + != Settings.Global.ZEN_MODE_OFF; + return disabledInConfig && isZenMode; + } } } diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index fbdba4f9206a..e27f3b20b314 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java @@ -19,16 +19,20 @@ package com.android.server.power.stats; import android.annotation.CurrentTimeMillisLong; import android.annotation.DurationMillisLong; import android.annotation.NonNull; +import android.os.BatteryConsumer; import android.os.BatteryStats; +import android.os.PersistableBundle; import android.os.UserHandle; import android.text.format.DateFormat; import android.util.IndentingPrintWriter; import android.util.Slog; +import android.util.SparseArray; import android.util.TimeUtils; import com.android.internal.os.PowerStats; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.power.stats.AggregatedPowerStatsConfig.PowerComponent; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -53,11 +57,14 @@ class AggregatedPowerStats { private static final int MAX_CLOCK_UPDATES = 100; private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats"; - private final PowerComponentAggregatedPowerStats[] mPowerComponentStats; + private final AggregatedPowerStatsConfig mConfig; + private final SparseArray<PowerComponentAggregatedPowerStats> mPowerComponentStats; + private final PowerComponentAggregatedPowerStats mGenericPowerComponent; static class ClockUpdate { public long monotonicTime; - @CurrentTimeMillisLong public long currentTime; + @CurrentTimeMillisLong + public long currentTime; } private final List<ClockUpdate> mClockUpdates = new ArrayList<>(); @@ -65,13 +72,35 @@ class AggregatedPowerStats { @DurationMillisLong private long mDurationMs; - AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { - List<AggregatedPowerStatsConfig.PowerComponent> configs = + AggregatedPowerStats(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { + mConfig = aggregatedPowerStatsConfig; + List<PowerComponent> configs = aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs(); - mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()]; + mPowerComponentStats = new SparseArray<>(configs.size()); for (int i = 0; i < configs.size(); i++) { - mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(this, configs.get(i)); + PowerComponent powerComponent = configs.get(i); + mPowerComponentStats.put(powerComponent.getPowerComponentId(), + new PowerComponentAggregatedPowerStats(this, powerComponent)); } + mGenericPowerComponent = createGenericPowerComponent(); + mPowerComponentStats.put(BatteryConsumer.POWER_COMPONENT_ANY, mGenericPowerComponent); + } + + private PowerComponentAggregatedPowerStats createGenericPowerComponent() { + PowerComponent config = new PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY); + config.trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE); + PowerComponentAggregatedPowerStats stats = + new PowerComponentAggregatedPowerStats(this, config); + stats.setPowerStatsDescriptor( + new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_ANY, 0, null, 0, 0, + new PersistableBundle())); + return stats; } /** @@ -79,7 +108,7 @@ class AggregatedPowerStats { * there may be multiple clock updates in one set of aggregated stats. * * @param monotonicTime monotonic time in milliseconds, see - * {@link com.android.internal.os.MonotonicClock} + * {@link com.android.internal.os.MonotonicClock} * @param currentTime current time in milliseconds, see {@link System#currentTimeMillis()} */ void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) { @@ -90,7 +119,7 @@ class AggregatedPowerStats { mClockUpdates.add(clockUpdate); } else { Slog.i(TAG, "Too many clock updates. Replacing the previous update with " - + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime)); + + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime)); mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate); } } @@ -119,66 +148,97 @@ class AggregatedPowerStats { return mDurationMs; } - PowerComponentAggregatedPowerStats getPowerComponentStats(int powerComponentId) { - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - if (stats.powerComponentId == powerComponentId) { - return stats; + List<PowerComponentAggregatedPowerStats> getPowerComponentStats() { + List<PowerComponentAggregatedPowerStats> list = new ArrayList<>( + mPowerComponentStats.size()); + for (int i = 0; i < mPowerComponentStats.size(); i++) { + PowerComponentAggregatedPowerStats stats = mPowerComponentStats.valueAt(i); + if (stats != mGenericPowerComponent) { + list.add(stats); } } - return null; + return list; + } + + PowerComponentAggregatedPowerStats getPowerComponentStats(int powerComponentId) { + return mPowerComponentStats.get(powerComponentId); + } + + void start(long timestampMs) { + for (int i = 0; i < mPowerComponentStats.size(); i++) { + PowerComponentAggregatedPowerStats component = mPowerComponentStats.valueAt(i); + component.getConfig().getProcessor().start(component, timestampMs); + } } void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) { - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - stats.setState(stateId, state, time); + for (int i = 0; i < mPowerComponentStats.size(); i++) { + mPowerComponentStats.valueAt(i).setState(stateId, state, time); } } void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) { - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - stats.setUidState(uid, stateId, state, time); + for (int i = 0; i < mPowerComponentStats.size(); i++) { + mPowerComponentStats.valueAt(i).setUidState(uid, stateId, state, time); } } boolean isCompatible(PowerStats powerStats) { int powerComponentId = powerStats.descriptor.powerComponentId; - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - if (stats.powerComponentId == powerComponentId && !stats.isCompatible(powerStats)) { - return false; - } - } - return true; + PowerComponentAggregatedPowerStats stats = mPowerComponentStats.get(powerComponentId); + return stats != null && stats.isCompatible(powerStats); } void addPowerStats(PowerStats powerStats, long time) { int powerComponentId = powerStats.descriptor.powerComponentId; - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - if (stats.powerComponentId == powerComponentId) { - stats.getConfig().getProcessor().addPowerStats(stats, powerStats, time); + PowerComponentAggregatedPowerStats stats = mPowerComponentStats.get(powerComponentId); + if (stats == null) { + PowerComponent powerComponent = mConfig.createPowerComponent(powerComponentId); + if (powerComponent == null) { + return; } + + stats = new PowerComponentAggregatedPowerStats(this, powerComponent); + stats.setPowerStatsDescriptor(powerStats.descriptor); + stats.copyStatesFrom(mGenericPowerComponent); + mPowerComponentStats.put(powerComponentId, stats); } + + PowerStatsProcessor processor = stats.getConfig().getProcessor(); + processor.addPowerStats(stats, powerStats, time); } public void noteStateChange(BatteryStats.HistoryItem item) { - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { + for (int i = 0; i < mPowerComponentStats.size(); i++) { + PowerComponentAggregatedPowerStats stats = mPowerComponentStats.valueAt(i); stats.getConfig().getProcessor().noteStateChange(stats, item); } } + void finish(long timestampMs) { + for (int i = 0; i < mPowerComponentStats.size(); i++) { + PowerComponentAggregatedPowerStats component = mPowerComponentStats.valueAt(i); + component.getConfig().getProcessor().finish(component, timestampMs); + } + } + void reset() { mClockUpdates.clear(); mDurationMs = 0; - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - stats.reset(); + for (int i = 0; i < mPowerComponentStats.size(); i++) { + mPowerComponentStats.valueAt(i).reset(); } } public void writeXml(TypedXmlSerializer serializer) throws IOException { serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS); - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - stats.writeXml(serializer); + for (int i = 0; i < mPowerComponentStats.size(); i++) { + PowerComponentAggregatedPowerStats stats = mPowerComponentStats.valueAt(i); + if (stats != mGenericPowerComponent) { + stats.writeXml(serializer); + } } serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS); serializer.flush(); @@ -200,23 +260,34 @@ class AggregatedPowerStats { case XML_TAG_AGGREGATED_POWER_STATS: inElement = true; break; - case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT: + case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT: { if (!inElement) { break; } int powerComponentId = parser.getAttributeInt(null, PowerComponentAggregatedPowerStats.XML_ATTR_ID); - for (PowerComponentAggregatedPowerStats powerComponent : - stats.mPowerComponentStats) { - if (powerComponent.powerComponentId == powerComponentId) { - if (!powerComponent.readFromXml(parser)) { - skipToEnd = true; - } - break; + + PowerComponentAggregatedPowerStats powerComponentStats = + stats.getPowerComponentStats(powerComponentId); + if (powerComponentStats == null) { + PowerComponent powerComponent = + aggregatedPowerStatsConfig.createPowerComponent( + powerComponentId); + if (powerComponent != null) { + powerComponentStats = new PowerComponentAggregatedPowerStats(stats, + powerComponent); + stats.mPowerComponentStats.put(powerComponentId, + powerComponentStats); + } + } + if (powerComponentStats != null) { + if (!powerComponentStats.readFromXml(parser)) { + skipToEnd = true; } } break; + } } } eventType = parser.next(); @@ -254,14 +325,14 @@ class AggregatedPowerStats { ipw.println("Device"); ipw.increaseIndent(); - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - stats.dumpDevice(ipw); + for (int i = 0; i < mPowerComponentStats.size(); i++) { + mPowerComponentStats.valueAt(i).dumpDevice(ipw); } ipw.decreaseIndent(); Set<Integer> uids = new HashSet<>(); - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - stats.collectUids(uids); + for (int i = 0; i < mPowerComponentStats.size(); i++) { + mPowerComponentStats.valueAt(i).collectUids(uids); } Integer[] allUids = uids.toArray(new Integer[uids.size()]); @@ -269,8 +340,8 @@ class AggregatedPowerStats { for (int uid : allUids) { ipw.println(UserHandle.formatUid(uid)); ipw.increaseIndent(); - for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { - stats.dumpUid(ipw, uid); + for (int i = 0; i < mPowerComponentStats.size(); i++) { + mPowerComponentStats.valueAt(i).dumpUid(ipw, uid); } ipw.decreaseIndent(); } diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java index 1ff7cb773e68..1f4a3915e1ff 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -17,12 +17,14 @@ package com.android.server.power.stats; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.BatteryConsumer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; /** * Configuration that controls how power stats are aggregated. It determines which state changes @@ -140,7 +142,7 @@ public class AggregatedPowerStatsConfig { } @NonNull - public PowerStatsProcessor getProcessor() { + PowerStatsProcessor getProcessor() { return mProcessor; } @@ -160,6 +162,8 @@ public class AggregatedPowerStatsConfig { } private final List<PowerComponent> mPowerComponents = new ArrayList<>(); + private PowerComponent mCustomPowerComponent; + private Supplier<PowerStatsProcessor> mCustomPowerStatsProcessorFactory; /** * Creates a configuration for the specified power component, which may be one of the @@ -199,10 +203,45 @@ public class AggregatedPowerStatsConfig { return powerComponent; } + /** + * Creates a configuration for custom power components, which are yet to be discovered + * dynamically through the integration with PowerStatsService. + */ + public PowerComponent trackCustomPowerComponents( + Supplier<PowerStatsProcessor> processorFactory) { + mCustomPowerStatsProcessorFactory = processorFactory; + mCustomPowerComponent = new PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY); + return mCustomPowerComponent; + } + + /** + * Returns configurations for all registered or dynamically discovered power components. + */ public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() { return mPowerComponents; } + /** + * Creates a configuration for a custom power component discovered dynamically through the + * integration with PowerStatsService. + */ + @Nullable + public PowerComponent createPowerComponent(int powerComponentId) { + if (mCustomPowerComponent == null) { + return null; + } + + PowerComponent powerComponent = new PowerComponent(powerComponentId); + powerComponent.trackDeviceStates(mCustomPowerComponent.mTrackedDeviceStates); + powerComponent.trackUidStates(mCustomPowerComponent.mTrackedUidStates); + + if (mCustomPowerStatsProcessorFactory != null) { + powerComponent.setProcessor(mCustomPowerStatsProcessorFactory.get()); + } + + return powerComponent; + } + private static final PowerStatsProcessor NO_OP_PROCESSOR = new PowerStatsProcessor() { @Override void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) { diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 322ed864fca6..eabc979c7b88 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -300,6 +300,7 @@ public class BatteryStatsImpl extends BatteryStats { private final BluetoothPowerStatsCollector mBluetoothPowerStatsCollector; private final CameraPowerStatsCollector mCameraPowerStatsCollector; private final GnssPowerStatsCollector mGnssPowerStatsCollector; + private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector; private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray(); private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever = new WifiPowerStatsCollector.WifiStatsRetriever() { @@ -11303,6 +11304,10 @@ public class BatteryStatsImpl extends BatteryStats { mGnssPowerStatsCollector = new GnssPowerStatsCollector(mPowerStatsCollectorInjector); mGnssPowerStatsCollector.addConsumer(this::recordPowerStats); + mCustomEnergyConsumerPowerStatsCollector = + new CustomEnergyConsumerPowerStatsCollector(mPowerStatsCollectorInjector); + mCustomEnergyConsumerPowerStatsCollector.addConsumer(this::recordPowerStats); + mStartCount++; initTimersAndCounters(); mOnBattery = mOnBatteryInternal = false; @@ -13922,6 +13927,11 @@ public class BatteryStatsImpl extends BatteryStats { * Read and record Rail Energy data. */ public void updateRailStatsLocked() { + if (mCustomEnergyConsumerPowerStatsCollector.isEnabled()) { + mCustomEnergyConsumerPowerStatsCollector.schedule(); + return; + } + if (mEnergyConsumerRetriever == null || !mTmpRailStats.isRailStatsAvailable()) { return; } @@ -14733,6 +14743,10 @@ public class BatteryStatsImpl extends BatteryStats { mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)); mGnssPowerStatsCollector.schedule(); + mCustomEnergyConsumerPowerStatsCollector.setEnabled( + mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_ANY)); + mCustomEnergyConsumerPowerStatsCollector.schedule(); + mSystemReady = true; } diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index ce0ee39d0a53..8127b8217bb0 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -113,7 +113,9 @@ public class BatteryUsageStatsProvider { mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile)); mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile)); - mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile)); + if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_ANY)) { + mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile)); + } mPowerCalculators.add(new UserPowerCalculator()); if (!com.android.server.power.optimization.Flags.disableSystemServicePowerAttr()) { diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java index 64c3446d2cd7..502337c789a2 100644 --- a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java @@ -16,16 +16,9 @@ package com.android.server.power.stats; -class BinaryStatePowerStatsLayout extends PowerStatsLayout { +class BinaryStatePowerStatsLayout extends EnergyConsumerPowerStatsLayout { BinaryStatePowerStatsLayout() { addDeviceSectionUsageDuration(); - // Add a section for consumed energy, even if the specific device does not - // have support EnergyConsumers. This is done to guarantee format compatibility between - // PowerStats created by a PowerStatsCollector and those produced by a PowerStatsProcessor. - addDeviceSectionEnergyConsumers(1); - addDeviceSectionPowerEstimate(); - addUidSectionUsageDuration(); - addUidSectionPowerEstimate(); } } diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java new file mode 100644 index 000000000000..11919019be4a --- /dev/null +++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; + +import java.util.ArrayList; +import java.util.List; + +public class CustomEnergyConsumerPowerStatsCollector extends PowerStatsCollector { + private static final EnergyConsumerPowerStatsLayout sLayout = + new EnergyConsumerPowerStatsLayout(); + private final EnergyConsumerPowerStatsCollector.Injector mInjector; + private List<EnergyConsumerPowerStatsCollector> mCollectors; + + CustomEnergyConsumerPowerStatsCollector(EnergyConsumerPowerStatsCollector.Injector injector) { + super(injector.getHandler(), 0, injector.getUidResolver(), injector.getClock()); + mInjector = injector; + } + + protected void ensureInitialized() { + if (mCollectors != null) { + return; + } + + ConsumedEnergyRetriever retriever = mInjector.getConsumedEnergyRetriever(); + int[] energyConsumerIds = retriever.getEnergyConsumerIds(EnergyConsumerType.OTHER); + int powerComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; + mCollectors = new ArrayList<>(energyConsumerIds.length); + for (int i = 0; i < energyConsumerIds.length; i++) { + String name = retriever.getEnergyConsumerName(energyConsumerIds[i]); + EnergyConsumerPowerStatsCollector collector = new EnergyConsumerPowerStatsCollector( + mInjector, powerComponentId++, name, energyConsumerIds[i], sLayout); + collector.setEnabled(true); + collector.addConsumer(this::deliverStats); + mCollectors.add(collector); + } + } + + @Override + public boolean schedule() { + if (!isEnabled()) { + return false; + } + + ensureInitialized(); + boolean success = false; + for (int i = 0; i < mCollectors.size(); i++) { + success |= mCollectors.get(i).schedule(); + } + return success; + } +} diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsProcessor.java new file mode 100644 index 000000000000..a86242ad0e02 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsProcessor.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import com.android.internal.os.PowerStats; + +import java.util.ArrayList; +import java.util.List; + +public class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor { + private static final EnergyConsumerPowerStatsLayout sLayout = + new EnergyConsumerPowerStatsLayout(); + private long[] mTmpDeviceStatsArray; + private long[] mTmpUidStatsArray; + private PowerEstimationPlan mPlan; + + @Override + void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) { + PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor(); + mTmpDeviceStatsArray = new long[descriptor.statsArrayLength]; + mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength]; + if (mPlan == null) { + mPlan = new PowerEstimationPlan(stats.getConfig()); + } + + computeDevicePowerEstimates(stats); + + List<Integer> uids = new ArrayList<>(); + stats.collectUids(uids); + + if (!uids.isEmpty()) { + computeUidPowerEstimates(stats, uids); + } + } + + private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) { + for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) { + DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i); + if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) { + continue; + } + + sLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, + uCtoMah(sLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0))); + stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray); + } + } + + private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, + List<Integer> uids) { + for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) { + UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i); + List<UidStateProportionalEstimate> proportionalEstimates = + uidStateEstimate.proportionalEstimates; + for (int j = proportionalEstimates.size() - 1; j >= 0; j--) { + UidStateProportionalEstimate proportionalEstimate = proportionalEstimates.get(j); + for (int k = uids.size() - 1; k >= 0; k--) { + int uid = uids.get(k); + if (stats.getUidStats(mTmpUidStatsArray, uid, + proportionalEstimate.stateValues)) { + sLayout.setUidPowerEstimate(mTmpUidStatsArray, + uCtoMah(sLayout.getUidConsumedEnergy(mTmpUidStatsArray, 0))); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } + } + } + } + } +} diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java index 2021f85b0210..cace94114aa0 100644 --- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java @@ -16,10 +16,13 @@ package com.android.server.power.stats; +import android.hardware.power.stats.EnergyConsumerAttribution; +import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.os.Handler; import android.os.PersistableBundle; import android.util.Slog; +import android.util.SparseLongArray; import com.android.internal.os.Clock; import com.android.internal.os.PowerStats; @@ -27,9 +30,7 @@ import com.android.internal.os.PowerStats; import java.util.function.IntSupplier; public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { - private static final String TAG = "CameraPowerStatsCollector"; - - private static final long CAMERA_ACTIVITY_REQUEST_TIMEOUT = 20000; + private static final String TAG = "EnergyConsumerPowerStatsCollector"; private static final long ENERGY_UNSPECIFIED = -1; @@ -48,21 +49,22 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { private final int mEnergyConsumerType; private final String mEnergyConsumerName; - private final BinaryStatePowerStatsLayout mLayout; + private final EnergyConsumerPowerStatsLayout mLayout; private boolean mIsInitialized; private PowerStats mPowerStats; private ConsumedEnergyRetriever mConsumedEnergyRetriever; private IntSupplier mVoltageSupplier; - private int[] mEnergyConsumerIds = new int[0]; + private int[] mEnergyConsumerIds; private long mLastConsumedEnergyUws = ENERGY_UNSPECIFIED; + private SparseLongArray mLastConsumerEnergyPerUid = new SparseLongArray(); private int mLastVoltageMv; private long mLastUpdateTimestamp; private boolean mFirstCollection = true; EnergyConsumerPowerStatsCollector(Injector injector, int powerComponentId, String powerComponentName, @EnergyConsumerType int energyConsumerType, - String energyConsumerName, BinaryStatePowerStatsLayout statsLayout) { + String energyConsumerName, EnergyConsumerPowerStatsLayout statsLayout) { super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(powerComponentName), injector.getUidResolver(), injector.getClock()); @@ -74,6 +76,21 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { mLayout = statsLayout; } + EnergyConsumerPowerStatsCollector(Injector injector, int powerComponentId, + String powerComponentName, int energyConsumerId, + EnergyConsumerPowerStatsLayout statsLayout) { + super(injector.getHandler(), + injector.getPowerStatsCollectionThrottlePeriod(powerComponentName), + injector.getUidResolver(), injector.getClock()); + mInjector = injector; + mPowerComponentId = powerComponentId; + mPowerComponentName = powerComponentName; + mEnergyConsumerIds = new int[]{energyConsumerId}; + mEnergyConsumerType = EnergyConsumerType.OTHER; + mEnergyConsumerName = null; + mLayout = statsLayout; + } + private boolean ensureInitialized() { if (mIsInitialized) { return true; @@ -85,9 +102,10 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever(); mVoltageSupplier = mInjector.getVoltageSupplier(); - mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(mEnergyConsumerType, - mEnergyConsumerName); - + if (mEnergyConsumerIds == null) { + mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(mEnergyConsumerType, + mEnergyConsumerName); + } PersistableBundle extras = new PersistableBundle(); mLayout.toExtras(extras); PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor( @@ -110,18 +128,23 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { return null; } - long consumedEnergy = 0; int voltageMv = mVoltageSupplier.getAsInt(); - if (voltageMv <= 0) { + int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; + if (averageVoltage <= 0) { Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv + " mV) when querying energy consumers"); - } else { - long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds); - if (energyUws != null) { - for (int i = energyUws.length - 1; i >= 0; i--) { - if (energyUws[i] != ENERGY_UNSPECIFIED) { - consumedEnergy += energyUws[i]; - } + return null; + } + + mLastVoltageMv = voltageMv; + + EnergyConsumerResult[] energy = + mConsumedEnergyRetriever.getConsumedEnergy(mEnergyConsumerIds); + long consumedEnergy = 0; + if (energy != null) { + for (int i = energy.length - 1; i >= 0; i--) { + if (energy[i].energyUWs != ENERGY_UNSPECIFIED) { + consumedEnergy += energy[i].energyUWs; } } } @@ -138,13 +161,47 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { return null; } - int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; - mLastVoltageMv = voltageMv; mLayout.setConsumedEnergy(mPowerStats.stats, 0, uJtoUc(energyDelta, averageVoltage)); + + for (int i = mPowerStats.uidStats.size() - 1; i >= 0; i--) { + mLayout.setUidConsumedEnergy(mPowerStats.uidStats.valueAt(i), 0, 0); + } + + if (energy != null) { + for (int i = energy.length - 1; i >= 0; i--) { + EnergyConsumerAttribution[] perUid = energy[i].attribution; + if (perUid == null) { + continue; + } + + for (EnergyConsumerAttribution attribution : perUid) { + int uid = mUidResolver.mapUid(attribution.uid); + long lastEnergy = mLastConsumerEnergyPerUid.get(uid); + long deltaEnergy = attribution.energyUWs - lastEnergy; + mLastConsumerEnergyPerUid.put(uid, attribution.energyUWs); + if (deltaEnergy <= 0) { + continue; + } + long[] uidStats = mPowerStats.uidStats.get(uid); + if (uidStats == null) { + uidStats = new long[mLayout.getUidStatsArrayLength()]; + mPowerStats.uidStats.put(uid, uidStats); + } + + mLayout.setUidConsumedEnergy(uidStats, 0, + mLayout.getUidConsumedEnergy(uidStats, 0) + deltaEnergy); + } + } + } long timestamp = mClock.elapsedRealtime(); mPowerStats.durationMs = timestamp - mLastUpdateTimestamp; mLastUpdateTimestamp = timestamp; mFirstCollection = false; return mPowerStats; } + + @Override + protected void onUidRemoved(int uid) { + mLastConsumerEnergyPerUid.delete(uid); + } } diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsLayout.java new file mode 100644 index 000000000000..8430f564813f --- /dev/null +++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsLayout.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.power.stats; + +class EnergyConsumerPowerStatsLayout extends PowerStatsLayout { + EnergyConsumerPowerStatsLayout() { + // Add a section for consumed energy, even if the specific device does not + // have support EnergyConsumers. This is done to guarantee format compatibility between + // PowerStats created by a PowerStatsCollector and those produced by a PowerStatsProcessor. + addDeviceSectionEnergyConsumers(1); + addDeviceSectionPowerEstimate(); + + // Allocate a cell for per-UID consumed energy attribution. We won't know whether the + // corresponding energy consumer does per-UID attribution until we get data from + // PowerStatsService. + addUidSectionEnergyConsumers(1); + addUidSectionPowerEstimate(); + } +} diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 85a229316b32..8384b2b8db82 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.UserHandle; import android.util.IndentingPrintWriter; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.os.PowerStats; @@ -44,6 +45,7 @@ import java.util.function.IntConsumer; * as part of the {@link PowerStats.Descriptor}. */ class PowerComponentAggregatedPowerStats { + private static final String TAG = "AggregatePowerStats"; static final String XML_TAG_POWER_COMPONENT = "power_component"; static final String XML_ATTR_ID = "id"; private static final String XML_TAG_DEVICE_STATS = "device-stats"; @@ -372,6 +374,37 @@ class PowerComponentAggregatedPowerStats { } } + void copyStatesFrom(PowerComponentAggregatedPowerStats source) { + if (source.mDeviceStates.length == mDeviceStates.length) { + System.arraycopy(source.mDeviceStates, 0, mDeviceStates, 0, mDeviceStates.length); + if (source.mDeviceStats != null) { + createDeviceStats(0); + if (mDeviceStats != null) { + mDeviceStats.copyStatesFrom(source.mDeviceStats); + } + } + } else { + Slog.wtf(TAG, "State configurations have different lengths: " + + source.mDeviceStates.length + " vs " + mDeviceStates.length); + } + for (int i = source.mUidStats.size() - 1; i >= 0; i--) { + int uid = source.mUidStats.keyAt(i); + UidStats sourceUidStats = source.mUidStats.valueAt(i); + if (sourceUidStats.states == null) { + continue; + } + UidStats uidStats = new UidStats(); + uidStats.states = Arrays.copyOf(sourceUidStats.states, sourceUidStats.states.length); + if (sourceUidStats.stats != null) { + createUidStats(uidStats, 0); + if (uidStats.stats != null) { + uidStats.stats.copyStatesFrom(sourceUidStats.stats); + } + } + mUidStats.put(uid, uidStats); + } + } + public void writeXml(TypedXmlSerializer serializer) throws IOException { // No stats aggregated - can skip writing XML altogether if (mPowerStatsDescriptor == null) { diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java index 6e7fdf175179..86f515cc3b4e 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java @@ -15,8 +15,8 @@ */ package com.android.server.power.stats; +import android.annotation.NonNull; import android.os.BatteryStats; -import android.util.SparseArray; import com.android.internal.os.BatteryStatsHistory; import com.android.internal.os.BatteryStatsHistoryIterator; @@ -32,20 +32,14 @@ public class PowerStatsAggregator { private static final long UNINITIALIZED = -1; private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; private final BatteryStatsHistory mHistory; - private final SparseArray<PowerStatsProcessor> mProcessors = new SparseArray<>(); private AggregatedPowerStats mStats; private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; - public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig, - BatteryStatsHistory history) { + public PowerStatsAggregator(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig, + @NonNull BatteryStatsHistory history) { mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig; mHistory = history; - for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig : - aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) { - PowerStatsProcessor processor = powerComponentsConfig.getProcessor(); - mProcessors.put(powerComponentsConfig.getPowerComponentId(), processor); - } } AggregatedPowerStatsConfig getConfig() { @@ -71,7 +65,7 @@ public class PowerStatsAggregator { mStats = new AggregatedPowerStats(mAggregatedPowerStatsConfig); } - start(mStats, startTimeMs); + mStats.start(startTimeMs); boolean clockUpdateAdded = false; long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED; @@ -138,7 +132,7 @@ public class PowerStatsAggregator { if (!mStats.isCompatible(item.powerStats)) { if (lastTime > baseTime) { mStats.setDuration(lastTime - baseTime); - finish(mStats, lastTime); + mStats.finish(lastTime); consumer.accept(mStats); } mStats.reset(); @@ -151,7 +145,7 @@ public class PowerStatsAggregator { } if (lastTime > baseTime) { mStats.setDuration(lastTime - baseTime); - finish(mStats, lastTime); + mStats.finish(lastTime); consumer.accept(mStats); } @@ -159,26 +153,6 @@ public class PowerStatsAggregator { } } - private void start(AggregatedPowerStats stats, long timestampMs) { - for (int i = 0; i < mProcessors.size(); i++) { - PowerComponentAggregatedPowerStats component = - stats.getPowerComponentStats(mProcessors.keyAt(i)); - if (component != null) { - mProcessors.valueAt(i).start(component, timestampMs); - } - } - } - - private void finish(AggregatedPowerStats stats, long timestampMs) { - for (int i = 0; i < mProcessors.size(); i++) { - PowerComponentAggregatedPowerStats component = - stats.getPowerComponentStats(mProcessors.keyAt(i)); - if (component != null) { - mProcessors.valueAt(i).finish(component, timestampMs); - } - } - } - /** * Reset to prepare for a new aggregation session. */ diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java index d442c61ee923..d9f6c1ff1444 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -16,6 +16,7 @@ package com.android.server.power.stats; +import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.power.stats.EnergyConsumer; import android.hardware.power.stats.EnergyConsumerResult; @@ -61,7 +62,6 @@ public abstract class PowerStatsCollector { private long mLastScheduledUpdateMs = -1; @GuardedBy("this") - @SuppressWarnings("unchecked") private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList(); public PowerStatsCollector(Handler handler, long throttlePeriodMs, @@ -90,7 +90,6 @@ public abstract class PowerStatsCollector { * Adds a consumer that will receive a callback every time a snapshot of stats is collected. * The method is thread safe. */ - @SuppressWarnings("unchecked") public void addConsumer(Consumer<PowerStats> consumer) { synchronized (this) { if (mConsumerList.contains(consumer)) { @@ -107,7 +106,6 @@ public abstract class PowerStatsCollector { * Removes a consumer. * The method is thread safe. */ - @SuppressWarnings("unchecked") public void removeConsumer(Consumer<PowerStats> consumer) { synchronized (this) { List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList); @@ -131,18 +129,6 @@ public abstract class PowerStatsCollector { return mEnabled; } - @SuppressWarnings("GuardedBy") // Field is volatile - public void collectAndDeliverStats() { - PowerStats stats = collectStats(); - if (stats == null) { - return; - } - List<Consumer<PowerStats>> consumerList = mConsumerList; - for (int i = consumerList.size() - 1; i >= 0; i--) { - consumerList.get(i).accept(stats); - } - } - /** * Schedules a stats snapshot collection, throttled in accordance with the * {@link #mThrottlePeriodMs} parameter. @@ -175,8 +161,30 @@ public abstract class PowerStatsCollector { return true; } + /** + * Performs a PowerStats collection pass and delivers the result to registered consumers. + */ + @SuppressWarnings("GuardedBy") // Field is volatile + public void collectAndDeliverStats() { + deliverStats(collectStats()); + } + @Nullable - protected abstract PowerStats collectStats(); + protected PowerStats collectStats() { + return null; + } + + @SuppressWarnings("GuardedBy") // Field is volatile + protected void deliverStats(PowerStats stats) { + if (stats == null) { + return; + } + + List<Consumer<PowerStats>> consumerList = mConsumerList; + for (int i = consumerList.size() - 1; i >= 0; i--) { + consumerList.get(i).accept(stats); + } + } /** * Collects a fresh stats snapshot and prints it to the supplied printer. @@ -231,10 +239,33 @@ public abstract class PowerStatsCollector { } interface ConsumedEnergyRetriever { + @NonNull int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType, String name); + String getEnergyConsumerName(int energyConsumerId); + @Nullable - long[] getConsumedEnergyUws(int[] energyConsumerIds); + EnergyConsumerResult[] getConsumedEnergy(int[] energyConsumerIds); + + @Nullable + default long[] getConsumedEnergyUws(int[] energyConsumerIds) { + EnergyConsumerResult[] results = getConsumedEnergy(energyConsumerIds); + if (results == null) { + return null; + } + + long[] energy = new long[energyConsumerIds.length]; + for (int i = 0; i < energyConsumerIds.length; i++) { + int id = energyConsumerIds[i]; + for (EnergyConsumerResult result : results) { + if (result.id == id) { + energy[i] = result.energyUWs; + break; + } + } + } + return energy; + } default int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType) { return getEnergyConsumerIds(energyConsumerType, null); @@ -243,24 +274,38 @@ public abstract class PowerStatsCollector { static class ConsumedEnergyRetrieverImpl implements ConsumedEnergyRetriever { private final PowerStatsInternal mPowerStatsInternal; + private EnergyConsumer[] mEnergyConsumers; ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal) { mPowerStatsInternal = powerStatsInternal; } - @Override - public int[] getEnergyConsumerIds(int energyConsumerType, String name) { + private void ensureEnergyConsumers() { + if (mEnergyConsumers != null) { + return; + } + if (mPowerStatsInternal == null) { - return new int[0]; + mEnergyConsumers = new EnergyConsumer[0]; + return; } - EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo(); - if (energyConsumerInfo == null) { + mEnergyConsumers = mPowerStatsInternal.getEnergyConsumerInfo(); + if (mEnergyConsumers == null) { + mEnergyConsumers = new EnergyConsumer[0]; + } + } + + @Override + public int[] getEnergyConsumerIds(int energyConsumerType, String name) { + ensureEnergyConsumers(); + + if (mEnergyConsumers.length == 0) { return new int[0]; } List<EnergyConsumer> energyConsumers = new ArrayList<>(); - for (EnergyConsumer energyConsumer : energyConsumerInfo) { + for (EnergyConsumer energyConsumer : mEnergyConsumers) { if (energyConsumer.type == energyConsumerType && (name == null || name.equals(energyConsumer.name))) { energyConsumers.add(energyConsumer); @@ -280,32 +325,50 @@ public abstract class PowerStatsCollector { } @Override - public long[] getConsumedEnergyUws(int[] energyConsumerIds) { + public EnergyConsumerResult[] getConsumedEnergy(int[] energyConsumerIds) { CompletableFuture<EnergyConsumerResult[]> future = mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds); - EnergyConsumerResult[] results = null; try { - results = future.get( - POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS); + return future.get(POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e); } - if (results == null) { - return null; + return null; + } + + @Override + public String getEnergyConsumerName(int energyConsumerId) { + ensureEnergyConsumers(); + + for (EnergyConsumer energyConsumer : mEnergyConsumers) { + if (energyConsumer.id == energyConsumerId) { + return sanitizeCustomPowerComponentName(energyConsumer); + } } - long[] energy = new long[energyConsumerIds.length]; - for (int i = 0; i < energyConsumerIds.length; i++) { - int id = energyConsumerIds[i]; - for (EnergyConsumerResult result : results) { - if (result.id == id) { - energy[i] = result.energyUWs; - break; - } + Slog.e(TAG, "Unsupported energy consumer ID " + energyConsumerId); + return "unsupported"; + } + + private String sanitizeCustomPowerComponentName(EnergyConsumer energyConsumer) { + String name = energyConsumer.name; + if (name == null || name.isBlank()) { + name = "CUSTOM_" + energyConsumer.id; + } + int length = name.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = name.charAt(i); + if (Character.isWhitespace(c)) { + sb.append(' '); + } else if (Character.isISOControl(c)) { + sb.append('_'); + } else { + sb.append(c); } } - return energy; + return sb.toString(); } } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java index f6b198a88fc2..4bba649cb97f 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -115,25 +115,16 @@ public class PowerStatsExporter { private void populateBatteryUsageStatsBuilder( BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats) { - AggregatedPowerStatsConfig config = mPowerStatsAggregator.getConfig(); - List<AggregatedPowerStatsConfig.PowerComponent> powerComponents = - config.getPowerComponentsAggregatedStatsConfigs(); - for (int i = powerComponents.size() - 1; i >= 0; i--) { - populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats, - powerComponents.get(i)); + List<PowerComponentAggregatedPowerStats> powerComponentStats = + stats.getPowerComponentStats(); + for (int i = powerComponentStats.size() - 1; i >= 0; i--) { + populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, powerComponentStats.get(i)); } } - private void populateBatteryUsageStatsBuilder( - BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats, - AggregatedPowerStatsConfig.PowerComponent powerComponent) { - int powerComponentId = powerComponent.getPowerComponentId(); - PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats( - powerComponentId); - if (powerComponentStats == null) { - return; - } - + private static void populateBatteryUsageStatsBuilder( + BatteryUsageStats.Builder batteryUsageStatsBuilder, + PowerComponentAggregatedPowerStats powerComponentStats) { PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor(); if (descriptor == null) { return; @@ -144,7 +135,8 @@ public class PowerStatsExporter { long[] deviceStats = new long[descriptor.statsArrayLength]; double[] totalPower = new double[1]; - MultiStateStats.States.forEachTrackedStateCombination(powerComponent.getDeviceStateConfig(), + MultiStateStats.States.forEachTrackedStateCombination( + powerComponentStats.getConfig().getDeviceStateConfig(), states -> { if (states[AggregatedPowerStatsConfig.STATE_POWER] != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) { @@ -161,29 +153,34 @@ public class PowerStatsExporter { AggregateBatteryConsumer.Builder deviceScope = batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); - deviceScope.addConsumedPower(powerComponentId, - totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED); + if (descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { + deviceScope.addConsumedPowerForCustomComponent(descriptor.powerComponentId, + totalPower[0]); + } else { + deviceScope.addConsumedPower(descriptor.powerComponentId, + totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED); + } if (layout.isUidPowerAttributionSupported()) { - populateUidBatteryConsumers(batteryUsageStatsBuilder, powerComponent, + populateUidBatteryConsumers(batteryUsageStatsBuilder, powerComponentStats, layout); } } private static void populateUidBatteryConsumers( BatteryUsageStats.Builder batteryUsageStatsBuilder, - AggregatedPowerStatsConfig.PowerComponent powerComponent, PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout) { + AggregatedPowerStatsConfig.PowerComponent powerComponent = powerComponentStats.getConfig(); int powerComponentId = powerComponent.getPowerComponentId(); PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor(); long[] uidStats = new long[descriptor.uidStatsArrayLength]; - boolean breakDownByProcState = - batteryUsageStatsBuilder.isProcessStateDataNeeded() + // TODO(b/347101393): add support for per-procstate breakdown for custom energy consumers + boolean breakDownByProcState = batteryUsageStatsBuilder.isProcessStateDataNeeded() && powerComponent - .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE] - .isTracked(); + .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked() + && powerComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; double[] powerByProcState = new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1]; @@ -224,19 +221,27 @@ public class PowerStatsExporter { powerAllProcStates += power; if (breakDownByProcState && procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { - builder.addConsumedPower(builder.getKey(powerComponentId, procState), - power, BatteryConsumer.POWER_MODEL_UNDEFINED); + builder.addConsumedPower(builder.getKey(powerComponentId, procState), power, + BatteryConsumer.POWER_MODEL_UNDEFINED); } } - builder.addConsumedPower(powerComponentId, powerAllProcStates, - BatteryConsumer.POWER_MODEL_UNDEFINED); + if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { + builder.addConsumedPowerForCustomComponent(powerComponentId, powerAllProcStates); + } else { + builder.addConsumedPower(powerComponentId, powerAllProcStates, + BatteryConsumer.POWER_MODEL_UNDEFINED); + } powerAllApps += powerAllProcStates; } AggregateBatteryConsumer.Builder allAppsScope = batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); - allAppsScope.addConsumedPower(powerComponentId, powerAllApps, - BatteryConsumer.POWER_MODEL_UNDEFINED); + if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { + allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps); + } else { + allAppsScope.addConsumedPower(powerComponentId, powerAllApps, + BatteryConsumer.POWER_MODEL_UNDEFINED); + } } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java index 9624fd28bb2c..62abfc62fd36 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java @@ -32,6 +32,8 @@ public class PowerStatsLayout { private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de"; private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; private static final String EXTRA_UID_DURATION_POSITION = "ud"; + private static final String EXTRA_UID_ENERGY_CONSUMERS_POSITION = "ue"; + private static final String EXTRA_UID_ENERGY_CONSUMERS_COUNT = "uec"; private static final String EXTRA_UID_POWER_POSITION = "up"; protected static final int UNSUPPORTED = -1; @@ -53,6 +55,8 @@ public class PowerStatsLayout { private int mDeviceEnergyConsumerCount; private int mDevicePowerEstimatePosition = UNSUPPORTED; private int mUidDurationPosition = UNSUPPORTED; + private int mUidEnergyConsumerPosition = UNSUPPORTED; + private int mUidEnergyConsumerCount; private int mUidPowerEstimatePosition = UNSUPPORTED; public PowerStatsLayout() { @@ -244,6 +248,36 @@ public class PowerStatsLayout { } /** + * Declares that the UID stats array has a section capturing EnergyConsumer data from + * PowerStatsService. + */ + public void addUidSectionEnergyConsumers(int energyConsumerCount) { + mUidEnergyConsumerPosition = addUidSection(energyConsumerCount, "energy", + FLAG_OPTIONAL); + mUidEnergyConsumerCount = energyConsumerCount; + } + + public int getUidEnergyConsumerCount() { + return mUidEnergyConsumerCount; + } + + /** + * Saves the accumulated energy for the specified rail the corresponding + * <code>stats</code> element. + */ + public void setUidConsumedEnergy(long[] stats, int index, long energy) { + stats[mUidEnergyConsumerPosition + index] = energy; + } + + /** + * Extracts the EnergyConsumer data from a uid stats array for the specified + * EnergyConsumer. + */ + public long getUidConsumedEnergy(long[] stats, int index) { + return stats[mUidEnergyConsumerPosition + index]; + } + + /** * Converts the supplied mAh power estimate to a long and saves it in the corresponding * element of <code>stats</code>. */ @@ -269,6 +303,8 @@ public class PowerStatsLayout { mDeviceEnergyConsumerCount); extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); extras.putInt(EXTRA_UID_DURATION_POSITION, mUidDurationPosition); + extras.putInt(EXTRA_UID_ENERGY_CONSUMERS_POSITION, mUidEnergyConsumerPosition); + extras.putInt(EXTRA_UID_ENERGY_CONSUMERS_COUNT, mUidEnergyConsumerCount); extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString()); extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString()); @@ -284,6 +320,8 @@ public class PowerStatsLayout { mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT); mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION); mUidDurationPosition = extras.getInt(EXTRA_UID_DURATION_POSITION); + mUidEnergyConsumerPosition = extras.getInt(EXTRA_UID_ENERGY_CONSUMERS_POSITION); + mUidEnergyConsumerCount = extras.getInt(EXTRA_UID_ENERGY_CONSUMERS_COUNT); mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION); } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java index f257e1a3a1e3..dfc8daa15c37 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java @@ -43,7 +43,7 @@ import java.util.List; * 2. For each UID, compute the proportion of the combined estimates in each state * and attribute the corresponding portion of the total power estimate in that state to the UID. */ -abstract class PowerStatsProcessor { +public abstract class PowerStatsProcessor { private static final String TAG = "PowerStatsProcessor"; private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0; diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java index 9756094e5af0..503a7268d5d3 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java @@ -108,9 +108,9 @@ final class HapticFeedbackCustomization { throws CustomizationParserException, IOException { try { return loadVibrationsInternal(res, vibratorInfo); - } catch (VibrationXmlParser.VibrationXmlParserException - | XmlParserException - | XmlPullParserException e) { + } catch (VibrationXmlParser.ParseFailedException + | XmlParserException + | XmlPullParserException e) { throw new CustomizationParserException( "Error parsing haptic feedback customization file.", e); } @@ -121,7 +121,6 @@ final class HapticFeedbackCustomization { Resources res, VibratorInfo vibratorInfo) throws CustomizationParserException, IOException, - VibrationXmlParser.VibrationXmlParserException, XmlParserException, XmlPullParserException { if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) { @@ -172,10 +171,6 @@ final class HapticFeedbackCustomization { ParsedVibration parsedVibration = VibrationXmlParser.parseElement( parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS); - if (parsedVibration == null) { - throw new CustomizationParserException( - "Unable to parse vibration element for effect " + effectId); - } VibrationEffect effect = parsedVibration.resolve(vibratorInfo); if (effect != null) { if (effect.getDuration() == Long.MAX_VALUE) { diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 84c37180d93c..6537228583ec 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -69,6 +69,7 @@ abstract class Vibration { CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER), CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON), CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED), + CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS), IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS), IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING), IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING), diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 3dcc7a6f76e5..5c15ccb55c65 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -193,6 +193,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } }; + @VisibleForTesting + final AppOpsManager.OnOpChangedInternalListener mAppOpsChangeListener = + new AppOpsManager.OnOpChangedInternalListener() { + @Override + public void onOpChanged(int op, String packageName) { + if (op != AppOpsManager.OP_VIBRATE) { + return; + } + synchronized (mLock) { + if (shouldCancelAppOpModeChangedLocked(mNextVibration)) { + clearNextVibrationLocked( + new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_APP_OPS)); + } + if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) { + mCurrentVibration.notifyCancelled(new Vibration.EndInfo( + Vibration.Status.CANCELLED_BY_APP_OPS), /* immediate= */ false); + } + } + } + }; + static native long nativeInit(OnSyncedVibrationCompleteListener listener); static native long nativeGetFinalizer(); @@ -238,6 +259,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mBatteryStatsService = injector.getBatteryStatsService(); mAppOps = mContext.getSystemService(AppOpsManager.class); + if (Flags.cancelByAppops()) { + mAppOps.startWatchingMode(AppOpsManager.OP_VIBRATE, null, mAppOpsChangeListener); + } PowerManager pm = context.getSystemService(PowerManager.class); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); @@ -1390,6 +1414,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @GuardedBy("mLock") + private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) { + if (conductor == null) { + return false; + } + return checkAppOpModeLocked(conductor.getVibration().callerInfo) + != AppOpsManager.MODE_ALLOWED; + } + + @GuardedBy("mLock") private void onAllVibratorsLocked(Consumer<VibratorController> consumer) { for (int i = 0; i < mVibrators.size(); i++) { consumer.accept(mVibrators.valueAt(i)); @@ -2461,9 +2494,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { try { ParsedVibration parsedVibration = VibrationXmlParser.parseDocument(new StringReader(xml)); - if (parsedVibration == null) { - throw new IllegalArgumentException("Error parsing vibration XML " + xml); - } VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo(); if (combinedVibratorInfo == null) { throw new IllegalStateException( diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 8fc696556e29..b8469474aa18 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -17,6 +17,7 @@ package com.android.server.wallpaper; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.WallpaperManager.COMMAND_REAPPLY; @@ -100,7 +101,6 @@ import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.StorageManager; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.IWallpaperService; @@ -2210,10 +2210,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId, boolean getCropped) { final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL); - if (!hasPrivilege) { - mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true, - Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId); - } + if (!hasPrivilege) checkPermission(MANAGE_EXTERNAL_STORAGE); wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d9dc7ba9ad12..21155bb7e29e 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -158,7 +158,6 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.ActivityRecord.State.DESTROYED; @@ -807,9 +806,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final LetterboxUiController mLetterboxUiController; /** - * The policy for transparent activities + * App Compat Facade */ - final TransparentPolicy mTransparentPolicy; + @NonNull + final AppCompatController mAppCompatController; /** * The scale to fit at least one side of the activity to its parent. If the activity uses @@ -1706,7 +1706,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (isState(RESUMED)) { newParent.setResumedActivity(this, "onParentChanged"); } - mTransparentPolicy.start(); + mAppCompatController.getTransparentPolicy().start(); } if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -1908,7 +1908,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) { - mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint, t); + mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint, t, getPendingTransaction()); } void updateLetterboxSurfaceIfNeeded(WindowState winHint) { @@ -2144,8 +2144,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Don't move below setOrientation(info.screenOrientation) since it triggers // getOverrideOrientation that requires having mLetterboxUiController // initialised. - mTransparentPolicy = new TransparentPolicy(this, mWmService.mLetterboxConfiguration); mLetterboxUiController = new LetterboxUiController(mWmService, this); + mAppCompatController = new AppCompatController(mWmService, this); mCameraCompatControlEnabled = mWmService.mContext.getResources() .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled); mResolveConfigHint = new TaskFragment.ConfigOverrideHint(); @@ -4505,6 +4505,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this); mTaskSupervisor.mStoppingActivities.remove(this); mLetterboxUiController.destroy(); + mAppCompatController.getTransparentPolicy().stop(); // 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 task not on the activity, @@ -6245,12 +6246,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } - // Check if there are any activities with different UID over the activity that is embedded - // in untrusted mode. Traverse bottom to top with boundary so that it will only check - // activities above this activity. + // Check if there are any activities with different UID occluding partially the activity + // that is embedded in untrusted mode. Traverse bottom to top with boundary so that it will + // only check activities above this activity. final ActivityRecord differentUidOverlayActivity = getTask().getActivity( - a -> !a.finishing && a.getUid() != getUid(), this /* boundary */, - false /* includeBoundary */, false /* traverseTopToBottom */); + a -> !a.finishing && a.getUid() != getUid() && Rect.intersects(a.getBounds(), + getBounds()), this /* boundary */, false /* includeBoundary */, + false /* traverseTopToBottom */); return differentUidOverlayActivity != null; } @@ -6563,7 +6565,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Schedule an idle timeout in case the app doesn't do it for us. mTaskSupervisor.scheduleIdleTimeout(this); - mTaskSupervisor.reportResumedActivityLocked(this); + mTaskSupervisor.mStoppingActivities.remove(this); + if (getDisplayArea().allResumedActivitiesComplete()) { + // Construct the compat environment at a relatively stable state if needed. + updateCompatDisplayInsets(); + mRootWindowContainer.executeAppTransitionForAllDisplay(); + } resumeKeyDispatchingLocked(); final Task rootTask = getRootTask(); @@ -6968,14 +6975,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateReportedVisibilityLocked(); } - /** - * Sets whether something has been visible in the task and returns {@code true} if the state - * is changed from invisible to visible. - */ - private boolean setTaskHasBeenVisible() { + /** Sets whether something has been visible in the task. */ + private void setTaskHasBeenVisible() { final boolean wasTaskVisible = task.getHasBeenVisible(); if (wasTaskVisible) { - return false; + return; } if (inTransition()) { // The deferring will be canceled until transition is ready so it won't dispatch @@ -6983,20 +6987,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A task.setDeferTaskAppear(true); } task.setHasBeenVisible(true); - return true; } void onStartingWindowDrawn() { - boolean wasTaskVisible = false; if (task != null) { mSplashScreenStyleSolidColor = true; - wasTaskVisible = !setTaskHasBeenVisible(); + setTaskHasBeenVisible(); + } + if (mStartingData == null || mStartingData.mIsDisplayed) { + return; } + mStartingData.mIsDisplayed = true; // The transition may not be executed if the starting process hasn't attached. But if the // starting window is drawn, the transition can start earlier. Exclude finishing and bubble // because it may be a trampoline. - if (!wasTaskVisible && mStartingData != null && !finishing && !mLaunchedFromBubble + if (app == null && !finishing && !mLaunchedFromBubble && mVisibleRequested && !mDisplayContent.mAppTransition.isReady() && !mDisplayContent.mAppTransition.isRunning() && mDisplayContent.isNextTransitionForward()) { @@ -7233,9 +7239,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A isInterestingAndDrawn = true; } } - } else if (mStartingData != null && w.isDrawn()) { - // The starting window for this container is drawn. - mStartingData.mIsDisplayed = true; } } @@ -7519,7 +7522,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * use an icon or solid color splash screen will be made by WmShell. */ private boolean shouldUseSolidColorSplashScreen(ActivityRecord sourceRecord, - boolean startActivity, ActivityOptions options, int resolvedTheme) { + boolean startActivity, ActivityOptions options, int resolvedTheme, + boolean newTask) { if (sourceRecord == null && !startActivity) { // Use simple style if this activity is not top activity. This could happen when adding // a splash screen window to the warm start activity which is re-create because top is @@ -7542,21 +7546,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Choose the default behavior when neither the ActivityRecord nor the activity theme have // specified a splash screen style. - - if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME || launchedFromUid == Process.SHELL_UID) { - return false; - } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { + if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { return true; } else { // Need to check sourceRecord in case this activity is launched from a service. if (sourceRecord == null) { sourceRecord = searchCandidateLaunchingActivity(); } - if (sourceRecord != null) { - return sourceRecord.mSplashScreenStyleSolidColor; + return sourceRecord.mSplashScreenStyleSolidColor; // follow previous activity + } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME + || launchedFromUid == Process.SHELL_UID) { + return !newTask; // only show icon for new task } - // Use an icon if the activity was launched from System for the first start. // Otherwise, must use solid color splash screen. return mLaunchSourceType != LAUNCH_SOURCE_TYPE_SYSTEM || !startActivity; @@ -7624,7 +7626,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A splashScreenTheme); mSplashScreenStyleSolidColor = shouldUseSolidColorSplashScreen(sourceRecord, startActivity, - startOptions, resolvedTheme); + startOptions, resolvedTheme, newTask); final boolean activityCreated = mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal(); @@ -8102,13 +8104,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Configuration.Orientation int getRequestedConfigurationOrientation(boolean forDisplay, @ActivityInfo.ScreenOrientation int requestedOrientation) { - if (mTransparentPolicy.hasInheritedOrientation()) { + if (mAppCompatController.getTransparentPolicy().hasInheritedOrientation()) { final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { return reverseConfigurationOrientation( - mTransparentPolicy.getInheritedOrientation()); + mAppCompatController.getTransparentPolicy().getInheritedOrientation()); } else { - return mTransparentPolicy.getInheritedOrientation(); + return mAppCompatController.getTransparentPolicy().getInheritedOrientation(); } } if (task != null && requestedOrientation == SCREEN_ORIENTATION_BEHIND) { @@ -8181,7 +8183,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) { - if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) { + if (mAppCompatController.getAppCompatCapability() + .getAppCompatOrientationCapability() + .shouldIgnoreRequestedOrientation(requestedOrientation)) { return; } final int originalRelaunchingCount = mPendingRelaunchCount; @@ -8284,7 +8288,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ @Override protected int getOverrideOrientation() { - return mLetterboxUiController.overrideOrientationIfNeeded(super.getOverrideOrientation()); + return mAppCompatController.getOrientationPolicy() + .overrideOrientationIfNeeded(super.getOverrideOrientation()); } /** @@ -8324,8 +8329,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable CompatDisplayInsets getCompatDisplayInsets() { - if (mTransparentPolicy.isRunning()) { - return mTransparentPolicy.getInheritedCompatDisplayInsets(); + if (mAppCompatController.getTransparentPolicy().isRunning()) { + return mAppCompatController.getTransparentPolicy().getInheritedCompatDisplayInsets(); } return mCompatDisplayInsets; } @@ -8488,7 +8493,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } mSizeCompatBounds = null; mCompatDisplayInsets = null; - mTransparentPolicy.clearInheritedCompatDisplayInsets(); + mAppCompatController.getTransparentPolicy().clearInheritedCompatDisplayInsets(); } @VisibleForTesting @@ -8675,7 +8680,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Nullable Rect getParentAppBoundsOverride() { - return Rect.copyOrNull(mResolveConfigHint.mTmpParentAppBoundsOverride); + return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride); } /** @@ -8821,8 +8826,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; } // TODO(b/256564921): Investigate if we need new metrics for translucent activities - if (mTransparentPolicy.isRunning()) { - return mTransparentPolicy.getInheritedAppCompatState(); + if (mAppCompatController.getTransparentPolicy().isRunning()) { + return mAppCompatController.getTransparentPolicy().getInheritedAppCompatState(); } if (mInSizeCompatModeForBounds) { return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; @@ -8860,7 +8865,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } final Rect screenResolvedBounds = mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds; - final Rect parentAppBounds = mResolveConfigHint.mTmpParentAppBoundsOverride; + final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride; final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); final float screenResolvedBoundsWidth = screenResolvedBounds.width(); final float parentAppBoundsWidth = parentAppBounds.width(); @@ -8975,7 +8980,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // We check if the current activity is transparent. In that case we need to // recomputeConfiguration of the first opaque activity beneath, to allow a // proper computation of the new bounds. - if (!mTransparentPolicy.applyOnOpaqueActivityBelow( + if (!mAppCompatController.getTransparentPolicy().applyOnOpaqueActivityBelow( ActivityRecord::recomputeConfiguration)) { onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); } @@ -9269,7 +9274,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private void resolveAspectRatioRestriction(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); - final Rect parentAppBounds = mResolveConfigHint.mTmpParentAppBoundsOverride; + final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride; final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use @@ -9310,7 +9315,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A : newParentConfiguration.windowConfiguration.getBounds(); final Rect containerAppBounds = useResolvedBounds ? new Rect(resolvedConfig.windowConfiguration.getAppBounds()) - : mResolveConfigHint.mTmpParentAppBoundsOverride; + : mResolveConfigHint.mParentAppBoundsOverride; final int requestedOrientation = getRequestedConfigurationOrientation(); final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; @@ -9435,7 +9440,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) { // Only allow to scale down. - mSizeCompatScale = mTransparentPolicy.findOpaqueNotFinishingActivityBelow() + mSizeCompatScale = mAppCompatController.getTransparentPolicy() + .findOpaqueNotFinishingActivityBelow() .map(activityRecord -> activityRecord.mSizeCompatScale) .orElseGet(() -> { final int contentW = resolvedAppBounds.width(); @@ -9448,7 +9454,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { - if (mTransparentPolicy.isRunning()) { + if (mAppCompatController.getTransparentPolicy().isRunning()) { // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity // is letterboxed. return false; @@ -9511,7 +9517,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A public Rect getBounds() { // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities final Rect superBounds = super.getBounds(); - return mTransparentPolicy.findOpaqueNotFinishingActivityBelow() + return mAppCompatController.getTransparentPolicy().findOpaqueNotFinishingActivityBelow() .map(ActivityRecord::getBounds) .orElseGet(() -> { if (mSizeCompatBounds != null) { @@ -9875,8 +9881,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns the min aspect ratio of this activity. */ float getMinAspectRatio() { - if (mTransparentPolicy.isRunning()) { - return mTransparentPolicy.getInheritedMinAspectRatio(); + if (mAppCompatController.getTransparentPolicy().isRunning()) { + return mAppCompatController.getTransparentPolicy().getInheritedMinAspectRatio(); } if (info.applicationInfo == null) { return info.getMinAspectRatio(); @@ -9926,8 +9932,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } float getMaxAspectRatio() { - if (mTransparentPolicy.isRunning()) { - return mTransparentPolicy.getInheritedMaxAspectRatio(); + if (mAppCompatController.getTransparentPolicy().isRunning()) { + return mAppCompatController.getTransparentPolicy().getInheritedMaxAspectRatio(); } return info.getMaxAspectRatio(); } @@ -10302,7 +10308,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (rootTask != null && rootTask.mTranslucentActivityWaiting == this) { rootTask.checkTranslucentActivityWaiting(null); } - final boolean andResume = shouldBeResumed(null /*activeActivity*/); + final boolean andResume = isState(RESUMED) || shouldBeResumed(null /*activeActivity*/); List<ResultInfo> pendingResults = null; List<ReferrerIntent> pendingNewIntents = null; if (andResume) { @@ -10815,7 +10821,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO, mLetterboxUiController.shouldOverrideMinAspectRatio()); proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP, - mLetterboxUiController.shouldIgnoreOrientationRequestLoop()); + mAppCompatController.getAppCompatCapability().getAppCompatOrientationCapability() + .shouldIgnoreOrientationRequestLoop()); proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP, mLetterboxUiController.shouldOverrideForceResizeApp()); proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS, @@ -11147,8 +11154,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean cancel) { // This override is just for getting metrics. allFinished needs to be checked before // finish because finish resets all the states. - final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup(); - if (syncGroup != null && group != getSyncGroup()) return; + if (isDifferentSyncGroup(group)) return; mLastAllReadyAtSync = allSyncFinished(); super.finishSync(outMergedTransaction, group, cancel); } diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 0e401ebc94b5..a0ef03095010 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -424,19 +424,13 @@ public class ActivityStartController { Intent intent = intents[i]; NeededUriGrants intentGrants = null; - // Refuse possible leaked file descriptors. - if (intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } + intent.prepareToEnterSystemServer(); // Get the flag earlier because the intent may be modified in resolveActivity below. final boolean componentSpecified = intent.getComponent() != null; // Don't modify the client's object! intent = new Intent(intent); - // Remove existing mismatch flag so it can be properly updated later - intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); - // Collect information about the target of the Intent. ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], 0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid, diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index e6d81324efa1..e9c08e0ac29d 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -718,13 +718,7 @@ class ActivityStarter { onExecutionStarted(); if (mRequest.intent != null) { - // Refuse possible leaked file descriptors - if (mRequest.intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - // Remove existing mismatch flag so it can be properly updated later - mRequest.intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); + mRequest.intent.prepareToEnterSystemServer(); } final LaunchingState launchingState; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index cfd5300417b4..2109f5d5ab8f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1318,12 +1318,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle bOptions) { enforceNotIsolatedCaller("startActivityIntentSender"); if (fillInIntent != null) { - // Refuse possible leaked file descriptors - if (fillInIntent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - // Remove existing mismatch flag so it can be properly updated later - fillInIntent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); + fillInIntent.prepareToEnterSystemServer(); } if (!(target instanceof PendingIntentRecord)) { @@ -1349,10 +1344,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle bOptions) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); + if (intent != null) { + intent.prepareToEnterSystemServer(); } + SafeActivityOptions options = SafeActivityOptions.fromBundle(bOptions); synchronized (mGlobalLock) { @@ -1367,8 +1362,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return false; } intent = new Intent(intent); - // Remove existing mismatch flag so it can be properly updated later - intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); // The caller is not allowed to change the data. intent.setDataAndType(r.intent.getData(), r.intent.getType()); // And we are resetting to find the next component... diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 3867d2d229ea..b6e6991656f2 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2064,21 +2064,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } - boolean reportResumedActivityLocked(ActivityRecord r) { - // A resumed activity cannot be stopping. remove from list - mStoppingActivities.remove(r); - - final Task rootTask = r.getRootTask(); - if (rootTask.getDisplayArea().allResumedActivitiesComplete()) { - mRootWindowContainer.ensureActivitiesVisible(); - // Make sure activity & window visibility should be identical - // for all displays in this stage. - mRootWindowContainer.executeAppTransitionForAllDisplay(); - return true; - } - return false; - } - // Called when WindowManager has finished animating the launchingBehind activity to the back. private void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) { final Task task = r.getTask(); diff --git a/services/core/java/com/android/server/wm/AppCompatCapability.java b/services/core/java/com/android/server/wm/AppCompatCapability.java new file mode 100644 index 000000000000..d4379e486ea1 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatCapability.java @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; +import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; +import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; +import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; +import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA; +import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; +import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; + +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; + +import android.annotation.NonNull; +import android.content.pm.PackageManager; + +import com.android.server.wm.utils.OptPropFactory; +import com.android.window.flags.Flags; + +import java.util.function.BooleanSupplier; + +/** + * Encapsulate logic related to operations guarded by an app override. + */ +public class AppCompatCapability { + + private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatCapability" : TAG_ATM; + + @NonNull + private final LetterboxConfiguration mLetterboxConfiguration; + + @NonNull + private final ActivityRecord mActivityRecord; + + // Corresponds to OVERRIDE_ANY_ORIENTATION_TO_USER + private final boolean mIsSystemOverrideToFullscreenEnabled; + // Corresponds to OVERRIDE_RESPECT_REQUESTED_ORIENTATION + private final boolean mIsOverrideRespectRequestedOrientationEnabled; + // Corresponds to OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA + private final boolean mIsOverrideOrientationOnlyForCameraEnabled; + + @NonNull + private final OptPropFactory.OptProp mFakeFocusOptProp; + @NonNull + private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp; + @NonNull + private final OptPropFactory.OptProp mCameraCompatAllowRefreshOptProp; + @NonNull + private final OptPropFactory.OptProp mCameraCompatEnableRefreshViaPauseOptProp; + @NonNull + private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp; + @NonNull + private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp; + @NonNull + private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp; + @NonNull + private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp; + @NonNull + private final OptPropFactory.OptProp mAllowUserAspectRatioOverrideOptProp; + @NonNull + private final OptPropFactory.OptProp mAllowUserAspectRatioFullscreenOverrideOptProp; + + private final AppCompatOrientationCapability mAppCompatOrientationCapability; + + AppCompatCapability(@NonNull WindowManagerService wmService, + @NonNull ActivityRecord activityRecord, + @NonNull LetterboxConfiguration letterboxConfiguration) { + mLetterboxConfiguration = letterboxConfiguration; + mActivityRecord = activityRecord; + final PackageManager packageManager = wmService.mContext.getPackageManager(); + + final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, + activityRecord.packageName); + + mAppCompatOrientationCapability = + new AppCompatOrientationCapability(optPropBuilder, mLetterboxConfiguration, + mActivityRecord); + + mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, + mLetterboxConfiguration::isCompatFakeFocusEnabled); + + final BooleanSupplier isCameraCompatTreatmentEnabled = asLazy( + mLetterboxConfiguration::isCameraCompatTreatmentEnabled); + mCameraCompatAllowForceRotationOptProp = optPropBuilder.create( + PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, + isCameraCompatTreatmentEnabled); + mCameraCompatAllowRefreshOptProp = optPropBuilder.create( + PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, + isCameraCompatTreatmentEnabled); + mCameraCompatEnableRefreshViaPauseOptProp = optPropBuilder.create( + PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, + isCameraCompatTreatmentEnabled); + + mAllowOrientationOverrideOptProp = optPropBuilder.create( + PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); + + mAllowDisplayOrientationOverrideOptProp = optPropBuilder.create( + PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE, + () -> mActivityRecord.mDisplayContent != null + && mActivityRecord.getTask() != null + && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest() + && !mActivityRecord.getTask().inMultiWindowMode() + && mActivityRecord.mDisplayContent.getNaturalOrientation() + == ORIENTATION_LANDSCAPE + ); + + mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create( + PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + mAllowForceResizeOverrideOptProp = optPropBuilder.create( + PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create( + PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, + mLetterboxConfiguration::isUserAppAspectRatioSettingsEnabled); + mAllowUserAspectRatioFullscreenOverrideOptProp = optPropBuilder.create( + PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, + mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled); + + mIsSystemOverrideToFullscreenEnabled = + isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER); + mIsOverrideRespectRequestedOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION); + mIsOverrideOrientationOnlyForCameraEnabled = + isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA); + } + + /** + * @return {@code true} if the App Compat Camera Policy is active for the current activity. + */ + boolean isCameraCompatTreatmentActive() { + final DisplayContent displayContent = mActivityRecord.mDisplayContent; + if (displayContent == null) { + return false; + } + return displayContent.mDisplayRotationCompatPolicy != null + && displayContent.mDisplayRotationCompatPolicy + .isTreatmentEnabledForActivity(mActivityRecord); + } + + @NonNull + AppCompatOrientationCapability getAppCompatOrientationCapability() { + return mAppCompatOrientationCapability; + } + + /** + * Whether sending compat fake focus for split screen resumed activities is enabled. Needed + * because some game engines wait to get focus before drawing the content of the app which isn't + * guaranteed by default in multi-window modes. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the treatment is enabled + * <li>Component property is NOT set to false + * <li>Component property is set to true or per-app override is enabled + * </ul> + */ + boolean shouldSendFakeFocus() { + return mFakeFocusOptProp.shouldEnableWithOverrideAndProperty( + isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS)); + } + + /** + * Whether activity is eligible for camera compat force rotation treatment. See {@link + * DisplayRotationCompatPolicy} for context. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the camera compat treatment is enabled. + * <li>Activity isn't opted out by the device manufacturer with override or by the app + * developers with the component property. + * </ul> + */ + boolean shouldForceRotateForCameraCompat() { + return mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty( + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION)); + } + + /** + * Whether activity is eligible for activity "refresh" after camera compat force rotation + * treatment. See {@link DisplayRotationCompatPolicy} for context. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the camera compat treatment is enabled. + * <li>Activity isn't opted out by the device manufacturer with override or by the app + * developers with the component property. + * </ul> + */ + boolean shouldRefreshActivityForCameraCompat() { + return mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty( + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH)); + } + + /** + * Whether activity should be "refreshed" after the camera compat force rotation treatment + * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped + * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the camera compat treatment is enabled. + * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the + * component property by the app developers. + * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device + * manufacturer with override / by the app developers with the component property. + * </ul> + */ + boolean shouldRefreshActivityViaPauseForCameraCompat() { + return mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty( + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE)); + } + + boolean isSystemOverrideToFullscreenEnabled(int userAspectRatio) { + return mIsSystemOverrideToFullscreenEnabled + && !mAllowOrientationOverrideOptProp.isFalse() + && (userAspectRatio == USER_MIN_ASPECT_RATIO_UNSET + || userAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN); + } + + boolean isAllowOrientationOverrideOptOut() { + return mAllowOrientationOverrideOptProp.isFalse(); + } + + /** + * Whether we should apply the min aspect ratio per-app override. When this override is applied + * the min aspect ratio given in the app's manifest will be overridden to the largest enabled + * aspect ratio treatment unless the app's manifest value is higher. The treatment will also + * apply if no value is provided in the manifest. + * + * <p>This method returns {@code true} when the following conditions are met: + * <ul> + * <li>Opt-out component property isn't enabled + * <li>Per-app override is enabled + * </ul> + */ + boolean shouldOverrideMinAspectRatio() { + return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( + isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO)); + } + + boolean isOverrideRespectRequestedOrientationEnabled() { + return mIsOverrideRespectRequestedOrientationEnabled; + } + + boolean isOverrideOrientationOnlyForCameraEnabled() { + return mIsOverrideOrientationOnlyForCameraEnabled; + } + + /** + * Whether activity is eligible for camera compatibility free-form treatment. + * + * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing + * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and + * provides changes to the camera and display orientation signals to match those expected on a + * portrait device in that orientation (for example, on a standard phone). + * + * <p>The treatment is enabled when the following conditions are met: + * <ul> + * <li>Property gating the camera compatibility free-form treatment is enabled. + * <li>Activity isn't opted out by the device manufacturer with override. + * </ul> + */ + boolean shouldApplyFreeformTreatmentForCameraCompat() { + return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled( + OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); + } + + + /** + * Whether we should apply the min aspect ratio per-app override only when an app is connected + * to the camera. + * When this override is applied the min aspect ratio given in the app's manifest will be + * overridden to the largest enabled aspect ratio treatment unless the app's manifest value + * is higher. The treatment will also apply if no value is provided in the manifest. + * + * <p>This method returns {@code true} when the following conditions are met: + * <ul> + * <li>Opt-out component property isn't enabled + * <li>Per-app override is enabled + * </ul> + */ + boolean shouldOverrideMinAspectRatioForCamera() { + return mActivityRecord.isCameraActive() + && mAllowMinAspectRatioOverrideOptProp + .shouldEnableWithOptInOverrideAndOptOutProperty( + isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)); + } + + /** + * Whether should fix display orientation to landscape natural orientation when a task is + * fullscreen and the display is ignoring orientation requests. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Opt-out component property isn't enabled + * <li>Opt-in per-app override is enabled + * <li>Task is in fullscreen. + * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled + * <li>Natural orientation of the display is landscape. + * </ul> + */ + boolean shouldUseDisplayLandscapeNaturalOrientation() { + return mAllowDisplayOrientationOverrideOptProp + .shouldEnableWithOptInOverrideAndOptOutProperty( + isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION)); + } + + /** + * Whether we should apply the force resize per-app override. When this override is applied it + * forces the packages it is applied to to be resizable. It won't change whether the app can be + * put into multi-windowing mode, but allow the app to resize without going into size-compat + * mode when the window container resizes, such as display size change or screen rotation. + * + * <p>This method returns {@code true} when the following conditions are met: + * <ul> + * <li>Opt-out component property isn't enabled + * <li>Per-app override is enabled + * </ul> + */ + boolean shouldOverrideForceResizeApp() { + return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( + isCompatChangeEnabled(FORCE_RESIZE_APP)); + } + + /** + * Whether we should enable users to resize the current app. + */ + boolean shouldEnableUserAspectRatioSettings() { + // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has + // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null, + // the current app doesn't opt-out so the first part of the predicate is true. + return !mAllowUserAspectRatioOverrideOptProp.isFalse() + && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() + && mActivityRecord.mDisplayContent != null + && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest(); + } + + boolean isUserFullscreenOverrideEnabled() { + if (mAllowUserAspectRatioOverrideOptProp.isFalse() + || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse() + || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) { + return false; + } + return true; + } + + /** + * Whether we should apply the force non resize per-app override. When this override is applied + * it forces the packages it is applied to to be non-resizable. + * + * <p>This method returns {@code true} when the following conditions are met: + * <ul> + * <li>Opt-out component property isn't enabled + * <li>Per-app override is enabled + * </ul> + */ + boolean shouldOverrideForceNonResizeApp() { + return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( + isCompatChangeEnabled(FORCE_NON_RESIZE_APP)); + } + + private boolean isCompatChangeEnabled(long overrideChangeId) { + return mActivityRecord.info.isChangeEnabled(overrideChangeId); + } + + @NonNull + static BooleanSupplier asLazy(@NonNull BooleanSupplier supplier) { + return new BooleanSupplier() { + private boolean mRead; + private boolean mValue; + + @Override + public boolean getAsBoolean() { + if (!mRead) { + mRead = true; + mValue = supplier.getAsBoolean(); + } + return mValue; + } + }; + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java new file mode 100644 index 000000000000..3ff0fcefd747 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.annotation.NonNull; + +/** + * Allows the interaction with all the app compat policies and configurations + */ +class AppCompatController { + + @NonNull + private final TransparentPolicy mTransparentPolicy; + @NonNull + private final AppCompatOrientationPolicy mOrientationPolicy; + @NonNull + private final AppCompatCapability mAppCompatCapability; + + AppCompatController(@NonNull WindowManagerService wmService, + @NonNull ActivityRecord activityRecord) { + mTransparentPolicy = new TransparentPolicy(activityRecord, + wmService.mLetterboxConfiguration); + mAppCompatCapability = new AppCompatCapability(wmService, activityRecord, + wmService.mLetterboxConfiguration); + // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with aspectRatio. + final LetterboxUiController tmpController = activityRecord.mLetterboxUiController; + mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, + mAppCompatCapability, tmpController::shouldApplyUserFullscreenOverride, + tmpController::shouldApplyUserMinAspectRatioOverride, + tmpController::isSystemOverrideToFullscreenEnabled); + } + + @NonNull + TransparentPolicy getTransparentPolicy() { + return mTransparentPolicy; + } + + @NonNull + AppCompatOrientationPolicy getOrientationPolicy() { + return mOrientationPolicy; + } + + @NonNull + AppCompatCapability getAppCompatCapability() { + return mAppCompatCapability; + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationCapability.java b/services/core/java/com/android/server/wm/AppCompatOrientationCapability.java new file mode 100644 index 000000000000..10f3e833f78a --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatOrientationCapability.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; +import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; +import static android.content.pm.ActivityInfo.screenOrientationToString; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; + +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.AppCompatCapability.asLazy; + +import android.annotation.NonNull; +import android.content.pm.ActivityInfo; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.wm.utils.OptPropFactory; + +import java.util.function.BooleanSupplier; +import java.util.function.LongSupplier; + +class AppCompatOrientationCapability { + + private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatCapability" : TAG_ATM; + + @NonNull + private final ActivityRecord mActivityRecord; + + @NonNull + private final OptPropFactory.OptProp mIgnoreRequestedOrientationOptProp; + @NonNull + private final OptPropFactory.OptProp mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp; + + @NonNull + final OrientationCapabilityState mOrientationCapabilityState; + + AppCompatOrientationCapability(@NonNull OptPropFactory optPropBuilder, + @NonNull LetterboxConfiguration letterboxConfiguration, + @NonNull ActivityRecord activityRecord) { + mActivityRecord = activityRecord; + mOrientationCapabilityState = new OrientationCapabilityState(mActivityRecord, + System::currentTimeMillis); + final BooleanSupplier isPolicyForIgnoringRequestedOrientationEnabled = asLazy( + letterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled); + mIgnoreRequestedOrientationOptProp = optPropBuilder.create( + PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, + isPolicyForIgnoringRequestedOrientationEnabled); + mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp = optPropBuilder.create( + PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED, + isPolicyForIgnoringRequestedOrientationEnabled); + } + + /** + * Whether should ignore app requested orientation in response to an app + * calling {@link android.app.Activity#setRequestedOrientation}. + * + * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation} + * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has + * landscape natural orientation which app developers don't expect. For example, the loop can + * look like this: + * <ol> + * <li>App sets default orientation to "unspecified" at runtime + * <li>App requests to "portrait" after checking some condition (e.g. display rotation). + * <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because + * app can't handle the corresponding config changes. + * <li>Loop goes back to (1) + * </ol> + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the treatment is enabled + * <li>Opt-out component property isn't enabled + * <li>Opt-in component property or per-app override are enabled + * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation} + * call from an app or camera compat force rotation treatment is active for the activity. + * <li>Orientation request loop detected and is not letterboxed for fixed orientation + * </ul> + */ + boolean shouldIgnoreRequestedOrientation( + @ActivityInfo.ScreenOrientation int requestedOrientation) { + if (mIgnoreRequestedOrientationOptProp.shouldEnableWithOverrideAndProperty( + isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION))) { + if (mOrientationCapabilityState.mIsRelaunchingAfterRequestedOrientationChanged) { + Slog.w(TAG, "Ignoring orientation update to " + + screenOrientationToString(requestedOrientation) + + " due to relaunching after setRequestedOrientation for " + + mActivityRecord); + return true; + } + if (isCameraCompatTreatmentActive()) { + Slog.w(TAG, "Ignoring orientation update to " + + screenOrientationToString(requestedOrientation) + + " due to camera compat treatment for " + mActivityRecord); + return true; + } + } + + if (shouldIgnoreOrientationRequestLoop()) { + Slog.w(TAG, "Ignoring orientation update to " + + screenOrientationToString(requestedOrientation) + + " as orientation request loop was detected for " + + mActivityRecord); + return true; + } + return false; + } + + /** + * Whether an app is calling {@link android.app.Activity#setRequestedOrientation} + * in a loop and orientation request should be ignored. + * + * <p>This should only be called once in response to + * {@link android.app.Activity#setRequestedOrientation}. See + * {@link #shouldIgnoreRequestedOrientation} for more details. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the treatment is enabled + * <li>Opt-out component property isn't enabled + * <li>Per-app override is enabled + * <li>App has requested orientation more than 2 times within 1-second + * timer and activity is not letterboxed for fixed orientation + * </ul> + */ + boolean shouldIgnoreOrientationRequestLoop() { + final boolean loopDetectionEnabled = isCompatChangeEnabled( + OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); + if (!mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp + .shouldEnableWithOptInOverrideAndOptOutProperty(loopDetectionEnabled)) { + return false; + } + mOrientationCapabilityState.updateOrientationRequestLoopState(); + + return mOrientationCapabilityState.shouldIgnoreRequestInLoop() + && !mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio(); + } + + /** + * Sets whether an activity is relaunching after the app has called {@link + * android.app.Activity#setRequestedOrientation}. + */ + void setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching) { + mOrientationCapabilityState + .mIsRelaunchingAfterRequestedOrientationChanged = isRelaunching; + } + + boolean getIsRelaunchingAfterRequestedOrientationChanged() { + return mOrientationCapabilityState.mIsRelaunchingAfterRequestedOrientationChanged; + } + + @VisibleForTesting + int getSetOrientationRequestCounter() { + return mOrientationCapabilityState.mSetOrientationRequestCounter; + } + + private boolean isCompatChangeEnabled(long overrideChangeId) { + return mActivityRecord.info.isChangeEnabled(overrideChangeId); + } + + /** + * @return {@code true} if the App Compat Camera Policy is active for the current activity. + */ + // TODO(b/346253439): Remove after defining dependency with Camera capabilities. + private boolean isCameraCompatTreatmentActive() { + DisplayContent displayContent = mActivityRecord.mDisplayContent; + if (displayContent == null) { + return false; + } + return displayContent.mDisplayRotationCompatPolicy != null + && displayContent.mDisplayRotationCompatPolicy + .isTreatmentEnabledForActivity(mActivityRecord); + } + + static class OrientationCapabilityState { + // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR + final boolean mIsOverrideToNosensorOrientationEnabled; + // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT + final boolean mIsOverrideToPortraitOrientationEnabled; + // Corresponds to OVERRIDE_ANY_ORIENTATION + final boolean mIsOverrideAnyOrientationEnabled; + // Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE + final boolean mIsOverrideToReverseLandscapeOrientationEnabled; + + private boolean mIsRelaunchingAfterRequestedOrientationChanged; + + // Used to determine reset of mSetOrientationRequestCounter if next app requested + // orientation is after timeout value + @VisibleForTesting + static final int SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS = 1000; + // Minimum value of mSetOrientationRequestCounter before qualifying as orientation request + // loop + @VisibleForTesting + static final int MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP = 2; + // Updated when ActivityRecord#setRequestedOrientation is called + private long mTimeMsLastSetOrientationRequest = 0; + // Counter for ActivityRecord#setRequestedOrientation + private int mSetOrientationRequestCounter = 0; + @VisibleForTesting + LongSupplier mCurrentTimeMillisSupplier; + + OrientationCapabilityState(@NonNull ActivityRecord activityRecord, + @NonNull LongSupplier currentTimeMillisSupplier) { + mCurrentTimeMillisSupplier = currentTimeMillisSupplier; + mIsOverrideToNosensorOrientationEnabled = + activityRecord.info.isChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR); + mIsOverrideToPortraitOrientationEnabled = + activityRecord.info.isChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT); + mIsOverrideAnyOrientationEnabled = + activityRecord.info.isChangeEnabled(OVERRIDE_ANY_ORIENTATION); + mIsOverrideToReverseLandscapeOrientationEnabled = activityRecord.info + .isChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE); + } + + /** + * @return {@code true} if we should start ignoring orientation in a orientation request + * loop. + */ + boolean shouldIgnoreRequestInLoop() { + return mSetOrientationRequestCounter >= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; + } + + /** + * Updates the orientation request counter using a specific timeout. + */ + void updateOrientationRequestLoopState() { + final long currTimeMs = mCurrentTimeMillisSupplier.getAsLong(); + final long elapsedTime = currTimeMs - mTimeMsLastSetOrientationRequest; + if (elapsedTime < SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS) { + mSetOrientationRequestCounter++; + } else { + mSetOrientationRequestCounter = 0; + } + mTimeMsLastSetOrientationRequest = currTimeMs; + } + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java new file mode 100644 index 000000000000..c505ff9de825 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; +import static android.content.pm.ActivityInfo.isFixedOrientation; +import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; +import static android.content.pm.ActivityInfo.screenOrientationToString; + +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; + +import android.annotation.NonNull; +import android.content.pm.ActivityInfo; +import android.util.Slog; + +import java.util.function.BooleanSupplier; + +/** + * Contains all the logic related to orientation in the context of app compatibility + */ +class AppCompatOrientationPolicy { + + private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatOrientationPolicy" : TAG_ATM; + + @NonNull + private final ActivityRecord mActivityRecord; + + @NonNull + private final AppCompatCapability mAppCompatCapability; + + @NonNull + private final BooleanSupplier mShouldApplyUserFullscreenOverride; + @NonNull + private final BooleanSupplier mShouldApplyUserMinAspectRatioOverride; + @NonNull + private final BooleanSupplier mIsSystemOverrideToFullscreenEnabled; + + // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with spectRatio component + AppCompatOrientationPolicy(@NonNull ActivityRecord activityRecord, + @NonNull AppCompatCapability appCompatCapability, + @NonNull BooleanSupplier shouldApplyUserFullscreenOverride, + @NonNull BooleanSupplier shouldApplyUserMinAspectRatioOverride, + @NonNull BooleanSupplier isSystemOverrideToFullscreenEnabled) { + mActivityRecord = activityRecord; + mAppCompatCapability = appCompatCapability; + mShouldApplyUserFullscreenOverride = shouldApplyUserFullscreenOverride; + mShouldApplyUserMinAspectRatioOverride = shouldApplyUserMinAspectRatioOverride; + mIsSystemOverrideToFullscreenEnabled = isSystemOverrideToFullscreenEnabled; + } + + @ActivityInfo.ScreenOrientation + int overrideOrientationIfNeeded(@ActivityInfo.ScreenOrientation int candidate) { + final DisplayContent displayContent = mActivityRecord.mDisplayContent; + final boolean isIgnoreOrientationRequestEnabled = displayContent != null + && displayContent.getIgnoreOrientationRequest(); + if (mShouldApplyUserFullscreenOverride.getAsBoolean() && isIgnoreOrientationRequestEnabled + // Do not override orientation to fullscreen for camera activities. + // Fixed-orientation activities are rarely tested in other orientations, and it + // often results in sideways or stretched previews. As the camera compat treatment + // targets fixed-orientation activities, overriding the orientation disables the + // treatment. + && !mActivityRecord.isCameraActive()) { + Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + + " for " + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_USER) + + " by user aspect ratio settings."); + return SCREEN_ORIENTATION_USER; + } + + // In some cases (e.g. Kids app) we need to map the candidate orientation to some other + // orientation. + candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate); + + if (mShouldApplyUserMinAspectRatioOverride.getAsBoolean() && (!isFixedOrientation(candidate) + || candidate == SCREEN_ORIENTATION_LOCKED)) { + Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + + " for " + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT) + + " by user aspect ratio settings."); + return SCREEN_ORIENTATION_PORTRAIT; + } + + if (mAppCompatCapability.isAllowOrientationOverrideOptOut()) { + return candidate; + } + + if (displayContent != null && mAppCompatCapability + .isOverrideOrientationOnlyForCameraEnabled() + && (displayContent.mDisplayRotationCompatPolicy == null + || !displayContent.mDisplayRotationCompatPolicy + .isActivityEligibleForOrientationOverride(mActivityRecord))) { + return candidate; + } + + // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(), + // which will always come first before this check as user override > device + // manufacturer override. + if (mIsSystemOverrideToFullscreenEnabled.getAsBoolean() && isIgnoreOrientationRequestEnabled + // Do not override orientation to fullscreen for camera activities. + // Fixed-orientation activities are rarely tested in other orientations, and it + // often results in sideways or stretched previews. As the camera compat treatment + // targets fixed-orientation activities, overriding the orientation disables the + // treatment. + && !mActivityRecord.isCameraActive()) { + Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + + " for " + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_USER)); + return SCREEN_ORIENTATION_USER; + } + + final AppCompatOrientationCapability.OrientationCapabilityState capabilityState = + mAppCompatCapability.getAppCompatOrientationCapability() + .mOrientationCapabilityState; + + if (capabilityState.mIsOverrideToReverseLandscapeOrientationEnabled + && (isFixedOrientationLandscape(candidate) + || capabilityState.mIsOverrideAnyOrientationEnabled)) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_REVERSE_LANDSCAPE)); + return SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } + + if (!capabilityState.mIsOverrideAnyOrientationEnabled && isFixedOrientation(candidate)) { + return candidate; + } + + if (capabilityState.mIsOverrideToPortraitOrientationEnabled) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT)); + return SCREEN_ORIENTATION_PORTRAIT; + } + + if (capabilityState.mIsOverrideToNosensorOrientationEnabled) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_NOSENSOR)); + return SCREEN_ORIENTATION_NOSENSOR; + } + + return candidate; + } + +} diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index e8faff621165..a8cc2ae161cf 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -348,6 +348,11 @@ class BLASTSyncEngine { wc.setSyncGroup(this); } wc.prepareSync(); + if (wc.mSyncState == WindowContainer.SYNC_STATE_NONE && wc.mSyncGroup != null) { + Slog.w(TAG, "addToSync: unset SyncGroup " + wc.mSyncGroup.mSyncId + + " for non-sync " + wc); + wc.mSyncGroup = null; + } if (mReady) { mWm.mWindowPlacerLocked.requestTraversal(); } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 1ce324f68569..5699fdd15c31 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -252,7 +252,8 @@ class BackNavigationController { // skip if one of participant activity is translucent backType = BackNavigationInfo.TYPE_CALLBACK; } else if (prevActivities.size() > 0) { - if (!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities)) { + if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities)) + && isAllActivitiesCreated(prevActivities)) { // We have another Activity in the same currentTask to go to final WindowContainer parent = currentActivity.getParent(); final boolean canCustomize = parent != null @@ -549,6 +550,17 @@ class BackNavigationController { return !prevActivities.isEmpty(); } + private static boolean isAllActivitiesCreated( + @NonNull ArrayList<ActivityRecord> prevActivities) { + for (int i = prevActivities.size() - 1; i >= 0; --i) { + final ActivityRecord check = prevActivities.get(i); + if (check.isState(ActivityRecord.State.INITIALIZING)) { + return false; + } + } + return !prevActivities.isEmpty(); + } + boolean isMonitoringTransition() { return mAnimationHandler.mComposed || mNavigationMonitor.isMonitorForRemote(); } diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 362d4efa0825..2aa7c0c0a401 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -20,6 +20,7 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.view.SurfaceControl.HIDDEN; import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND; +import android.annotation.NonNull; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; @@ -209,16 +210,18 @@ public class Letterbox { return false; } - public void applySurfaceChanges(SurfaceControl.Transaction t) { + /** Applies surface changes such as colour, window crop, position and input info. */ + public void applySurfaceChanges(@NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction inputT) { if (useFullWindowSurface()) { - mFullWindowSurface.applySurfaceChanges(t); + mFullWindowSurface.applySurfaceChanges(t, inputT); for (LetterboxSurface surface : mSurfaces) { surface.remove(); } } else { for (LetterboxSurface surface : mSurfaces) { - surface.applySurfaceChanges(t); + surface.applySurfaceChanges(t, inputT); } mFullWindowSurface.remove(); @@ -418,7 +421,8 @@ public class Letterbox { return Math.max(0, mLayoutFrameGlobal.height()); } - public void applySurfaceChanges(SurfaceControl.Transaction t) { + public void applySurfaceChanges(@NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction inputT) { if (!needsApplySurfaceChanges()) { // Nothing changed. return; @@ -446,7 +450,7 @@ public class Letterbox { } if (mSurface != null && mInputInterceptor != null) { mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative); - t.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle); + inputT.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle); } } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 14a0467b1d7b..e0d2035118ad 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -18,33 +18,7 @@ package com.android.server.wm; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; -import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; -import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; -import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER; -import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; -import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT; -import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; -import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; -import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; -import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; -import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA; -import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; -import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION; -import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; -import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; -import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; -import static android.content.pm.ActivityInfo.isFixedOrientation; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; -import static android.content.pm.ActivityInfo.screenOrientationToString; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; @@ -53,22 +27,9 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SI import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; -import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; -import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; -import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER; @@ -105,7 +66,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; -import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -126,31 +86,17 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; -import com.android.server.wm.utils.OptPropFactory; -import com.android.server.wm.utils.OptPropFactory.OptProp; import com.android.window.flags.Flags; import java.io.PrintWriter; -import java.util.function.BooleanSupplier; /** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */ // TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in // SizeCompatTests and LetterboxTests but not all. -// TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the -// hierarchy in addition to ActivityRecord (Task, DisplayArea, ...). -// TODO(b/263021211): Consider renaming to more generic CompatUIController. final class LetterboxUiController { private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; - // Minimum value of mSetOrientationRequestCounter before qualifying as orientation request loop - @VisibleForTesting - static final int MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP = 2; - // Used to determine reset of mSetOrientationRequestCounter if next app requested - // orientation is after timeout value - @VisibleForTesting - static final int SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS = 1000; - private final Point mTmpPoint = new Point(); private final LetterboxConfiguration mLetterboxConfiguration; @@ -159,43 +105,9 @@ final class LetterboxUiController { // TODO(b/265576778): Cache other overrides as well. - // Corresponds to OVERRIDE_ANY_ORIENTATION - private final boolean mIsOverrideAnyOrientationEnabled; - // Corresponds to OVERRIDE_ANY_ORIENTATION_TO_USER - private final boolean mIsSystemOverrideToFullscreenEnabled; - // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT - private final boolean mIsOverrideToPortraitOrientationEnabled; - // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR - private final boolean mIsOverrideToNosensorOrientationEnabled; - // Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE - private final boolean mIsOverrideToReverseLandscapeOrientationEnabled; - // Corresponds to OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA - private final boolean mIsOverrideOrientationOnlyForCameraEnabled; - // Corresponds to OVERRIDE_RESPECT_REQUESTED_ORIENTATION - private final boolean mIsOverrideRespectRequestedOrientationEnabled; - - @NonNull - private final OptProp mAllowOrientationOverrideOptProp; - @NonNull - private final OptProp mAllowDisplayOrientationOverrideOptProp; - @NonNull - private final OptProp mAllowMinAspectRatioOverrideOptProp; - @NonNull - private final OptProp mAllowForceResizeOverrideOptProp; - - @NonNull - private final OptProp mAllowUserAspectRatioOverrideOptProp; - @NonNull - private final OptProp mAllowUserAspectRatioFullscreenOverrideOptProp; private boolean mShowWallpaperForLetterboxBackground; - // Updated when ActivityRecord#setRequestedOrientation is called - private long mTimeMsLastSetOrientationRequest = 0; - - // Counter for ActivityRecord#setRequestedOrientation - private int mSetOrientationRequestCounter = 0; - // TODO(b/315140179): Make mUserAspectRatio final // The min aspect ratio override set by user @PackageManager.UserMinAspectRatio @@ -204,31 +116,11 @@ final class LetterboxUiController { @Nullable private Letterbox mLetterbox; - @NonNull - private final OptProp mCameraCompatAllowForceRotationOptProp; - - @NonNull - private final OptProp mCameraCompatAllowRefreshOptProp; - - @NonNull - private final OptProp mCameraCompatEnableRefreshViaPauseOptProp; - // Whether activity "refresh" was requested but not finished in // ActivityRecord#activityResumedLocked following the camera compat force rotation in // DisplayRotationCompatPolicy. private boolean mIsRefreshRequested; - @NonNull - private final OptProp mIgnoreRequestedOrientationOptProp; - - @NonNull - private final OptProp mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp; - - @NonNull - private final OptProp mFakeFocusOptProp; - - private boolean mIsRelaunchingAfterRequestedOrientationChanged; - private boolean mLastShouldShowLetterboxUi; private boolean mDoubleTapEvent; @@ -242,76 +134,6 @@ final class LetterboxUiController { // is created in its constructor. It shouldn't be used in this constructor but it's safe // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; - - PackageManager packageManager = wmService.mContext.getPackageManager(); - - final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, - activityRecord.packageName); - - final BooleanSupplier isPolicyForIgnoringRequestedOrientationEnabled = asLazy( - mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled); - mIgnoreRequestedOrientationOptProp = optPropBuilder.create( - PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, - isPolicyForIgnoringRequestedOrientationEnabled); - mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp = optPropBuilder.create( - PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED, - isPolicyForIgnoringRequestedOrientationEnabled); - - mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, - mLetterboxConfiguration::isCompatFakeFocusEnabled); - - final BooleanSupplier isCameraCompatTreatmentEnabled = asLazy( - mLetterboxConfiguration::isCameraCompatTreatmentEnabled); - mCameraCompatAllowForceRotationOptProp = optPropBuilder.create( - PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, - isCameraCompatTreatmentEnabled); - mCameraCompatAllowRefreshOptProp = optPropBuilder.create( - PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, - isCameraCompatTreatmentEnabled); - mCameraCompatEnableRefreshViaPauseOptProp = optPropBuilder.create( - PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, - isCameraCompatTreatmentEnabled); - - mAllowOrientationOverrideOptProp = optPropBuilder.create( - PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); - - mAllowDisplayOrientationOverrideOptProp = optPropBuilder.create( - PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE, - () -> mActivityRecord.mDisplayContent != null - && mActivityRecord.getTask() != null - && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest() - && !mActivityRecord.getTask().inMultiWindowMode() - && mActivityRecord.mDisplayContent.getNaturalOrientation() - == ORIENTATION_LANDSCAPE - ); - - mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create( - PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); - - mAllowForceResizeOverrideOptProp = optPropBuilder.create( - PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); - - mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create( - PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, - mLetterboxConfiguration::isUserAppAspectRatioSettingsEnabled); - - mAllowUserAspectRatioFullscreenOverrideOptProp = optPropBuilder.create( - PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, - mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled); - - mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION); - mIsSystemOverrideToFullscreenEnabled = - isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER); - mIsOverrideToPortraitOrientationEnabled = - isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT); - mIsOverrideToReverseLandscapeOrientationEnabled = - isCompatChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE); - mIsOverrideToNosensorOrientationEnabled = - isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR); - mIsOverrideOrientationOnlyForCameraEnabled = - isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA); - mIsOverrideRespectRequestedOrientationEnabled = - isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION); } /** Cleans up {@link Letterbox} if it exists.*/ @@ -320,7 +142,6 @@ final class LetterboxUiController { mLetterbox.destroy(); mLetterbox = null; } - mActivityRecord.mTransparentPolicy.stop(); } void onMovedToDisplay(int displayId) { @@ -329,103 +150,11 @@ final class LetterboxUiController { } } - /** - * Whether should ignore app requested orientation in response to an app - * calling {@link android.app.Activity#setRequestedOrientation}. - * - * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation} - * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has - * landscape natural orientation which app developers don't expect. For example, the loop can - * look like this: - * <ol> - * <li>App sets default orientation to "unspecified" at runtime - * <li>App requests to "portrait" after checking some condition (e.g. display rotation). - * <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because - * app can't handle the corresponding config changes. - * <li>Loop goes back to (1) - * </ol> - * - * <p>This treatment is enabled when the following conditions are met: - * <ul> - * <li>Flag gating the treatment is enabled - * <li>Opt-out component property isn't enabled - * <li>Opt-in component property or per-app override are enabled - * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation} - * call from an app or camera compat force rotation treatment is active for the activity. - * <li>Orientation request loop detected and is not letterboxed for fixed orientation - * </ul> - */ - boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) { - if (mIgnoreRequestedOrientationOptProp.shouldEnableWithOverrideAndProperty( - isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION))) { - if (mIsRelaunchingAfterRequestedOrientationChanged) { - Slog.w(TAG, "Ignoring orientation update to " - + screenOrientationToString(requestedOrientation) - + " due to relaunching after setRequestedOrientation for " - + mActivityRecord); - return true; - } - if (isCameraCompatTreatmentActive()) { - Slog.w(TAG, "Ignoring orientation update to " - + screenOrientationToString(requestedOrientation) - + " due to camera compat treatment for " + mActivityRecord); - return true; - } - } - - if (shouldIgnoreOrientationRequestLoop()) { - Slog.w(TAG, "Ignoring orientation update to " - + screenOrientationToString(requestedOrientation) - + " as orientation request loop was detected for " - + mActivityRecord); - return true; - } - return false; - } - - /** - * Whether an app is calling {@link android.app.Activity#setRequestedOrientation} - * in a loop and orientation request should be ignored. - * - * <p>This should only be called once in response to - * {@link android.app.Activity#setRequestedOrientation}. See - * {@link #shouldIgnoreRequestedOrientation} for more details. - * - * <p>This treatment is enabled when the following conditions are met: - * <ul> - * <li>Flag gating the treatment is enabled - * <li>Opt-out component property isn't enabled - * <li>Per-app override is enabled - * <li>App has requested orientation more than 2 times within 1-second - * timer and activity is not letterboxed for fixed orientation - * </ul> - */ - boolean shouldIgnoreOrientationRequestLoop() { - final boolean loopDetectionEnabled = isCompatChangeEnabled( - OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); - if (!mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp - .shouldEnableWithOptInOverrideAndOptOutProperty(loopDetectionEnabled)) { - return false; - } - - final long currTimeMs = System.currentTimeMillis(); - if (currTimeMs - mTimeMsLastSetOrientationRequest - < SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS) { - mSetOrientationRequestCounter += 1; - } else { - // Resets app setOrientationRequest counter if timed out - mSetOrientationRequestCounter = 0; - } - // Update time last called - mTimeMsLastSetOrientationRequest = currTimeMs; - - return mSetOrientationRequestCounter >= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP - && !mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio(); - } @VisibleForTesting int getSetOrientationRequestCounter() { - return mSetOrientationRequestCounter; + return getAppCompatCapability().getAppCompatOrientationCapability() + .getSetOrientationRequestCounter(); } /** @@ -441,8 +170,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldSendFakeFocus() { - return mFakeFocusOptProp.shouldEnableWithOverrideAndProperty( - isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS)); + return getAppCompatCapability().shouldSendFakeFocus(); } /** @@ -458,8 +186,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldOverrideMinAspectRatio() { - return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( - isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO)); + return getAppCompatCapability().shouldOverrideMinAspectRatio(); } /** @@ -476,10 +203,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldOverrideMinAspectRatioForCamera() { - return mActivityRecord.isCameraActive() - && mAllowMinAspectRatioOverrideOptProp - .shouldEnableWithOptInOverrideAndOptOutProperty( - isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)); + return getAppCompatCapability().shouldOverrideMinAspectRatioForCamera(); } /** @@ -495,8 +219,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldOverrideForceResizeApp() { - return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( - isCompatChangeEnabled(FORCE_RESIZE_APP)); + return getAppCompatCapability().shouldOverrideForceResizeApp(); } /** @@ -510,8 +233,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldOverrideForceNonResizeApp() { - return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( - isCompatChangeEnabled(FORCE_NON_RESIZE_APP)); + return getAppCompatCapability().shouldOverrideForceNonResizeApp(); } /** @@ -519,7 +241,8 @@ final class LetterboxUiController { * android.app.Activity#setRequestedOrientation}. */ void setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching) { - mIsRelaunchingAfterRequestedOrientationChanged = isRelaunching; + getAppCompatCapability().getAppCompatOrientationCapability() + .setRelaunchingAfterRequestedOrientationChanged(isRelaunching); } /** @@ -534,7 +257,7 @@ final class LetterboxUiController { } boolean isOverrideRespectRequestedOrientationEnabled() { - return mIsOverrideRespectRequestedOrientationEnabled; + return getAppCompatCapability().isOverrideRespectRequestedOrientationEnabled(); } /** @@ -551,101 +274,11 @@ final class LetterboxUiController { * </ul> */ boolean shouldUseDisplayLandscapeNaturalOrientation() { - return mAllowDisplayOrientationOverrideOptProp - .shouldEnableWithOptInOverrideAndOptOutProperty( - isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION)); - } - - @ScreenOrientation - int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { - final DisplayContent displayContent = mActivityRecord.mDisplayContent; - final boolean isIgnoreOrientationRequestEnabled = displayContent != null - && displayContent.getIgnoreOrientationRequest(); - if (shouldApplyUserFullscreenOverride() && isIgnoreOrientationRequestEnabled - // Do not override orientation to fullscreen for camera activities. - // Fixed-orientation activities are rarely tested in other orientations, and it - // often results in sideways or stretched previews. As the camera compat treatment - // targets fixed-orientation activities, overriding the orientation disables the - // treatment. - && !mActivityRecord.isCameraActive()) { - Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " - + mActivityRecord + " is overridden to " - + screenOrientationToString(SCREEN_ORIENTATION_USER) - + " by user aspect ratio settings."); - return SCREEN_ORIENTATION_USER; - } - - // In some cases (e.g. Kids app) we need to map the candidate orientation to some other - // orientation. - candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate); - - if (shouldApplyUserMinAspectRatioOverride() && (!isFixedOrientation(candidate) - || candidate == SCREEN_ORIENTATION_LOCKED)) { - Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " - + mActivityRecord + " is overridden to " - + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT) - + " by user aspect ratio settings."); - return SCREEN_ORIENTATION_PORTRAIT; - } - - if (mAllowOrientationOverrideOptProp.isFalse()) { - return candidate; - } - - if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null - && (displayContent.mDisplayRotationCompatPolicy == null - || !displayContent.mDisplayRotationCompatPolicy - .isActivityEligibleForOrientationOverride(mActivityRecord))) { - return candidate; - } - - // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(), - // which will always come first before this check as user override > device - // manufacturer override. - if (isSystemOverrideToFullscreenEnabled() && isIgnoreOrientationRequestEnabled - // Do not override orientation to fullscreen for camera activities. - // Fixed-orientation activities are rarely tested in other orientations, and it - // often results in sideways or stretched previews. As the camera compat treatment - // targets fixed-orientation activities, overriding the orientation disables the - // treatment. - && !mActivityRecord.isCameraActive()) { - Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " - + mActivityRecord + " is overridden to " - + screenOrientationToString(SCREEN_ORIENTATION_USER)); - return SCREEN_ORIENTATION_USER; - } - - if (mIsOverrideToReverseLandscapeOrientationEnabled - && (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) { - Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " - + mActivityRecord + " is overridden to " - + screenOrientationToString(SCREEN_ORIENTATION_REVERSE_LANDSCAPE)); - return SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - } - - if (!mIsOverrideAnyOrientationEnabled && isFixedOrientation(candidate)) { - return candidate; - } - - if (mIsOverrideToPortraitOrientationEnabled) { - Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " - + mActivityRecord + " is overridden to " - + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT)); - return SCREEN_ORIENTATION_PORTRAIT; - } - - if (mIsOverrideToNosensorOrientationEnabled) { - Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " - + mActivityRecord + " is overridden to " - + screenOrientationToString(SCREEN_ORIENTATION_NOSENSOR)); - return SCREEN_ORIENTATION_NOSENSOR; - } - - return candidate; + return getAppCompatCapability().shouldUseDisplayLandscapeNaturalOrientation(); } boolean isOverrideOrientationOnlyForCameraEnabled() { - return mIsOverrideOrientationOnlyForCameraEnabled; + return getAppCompatCapability().isOverrideOrientationOnlyForCameraEnabled(); } /** @@ -660,8 +293,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldRefreshActivityForCameraCompat() { - return mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty( - isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH)); + return getAppCompatCapability().shouldRefreshActivityForCameraCompat(); } /** @@ -679,8 +311,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldRefreshActivityViaPauseForCameraCompat() { - return mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty( - isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE)); + return getAppCompatCapability().shouldRefreshActivityViaPauseForCameraCompat(); } /** @@ -695,8 +326,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldForceRotateForCameraCompat() { - return mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty( - isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION)); + return getAppCompatCapability().shouldForceRotateForCameraCompat(); } /** @@ -714,18 +344,7 @@ final class LetterboxUiController { * </ul> */ boolean shouldApplyFreeformTreatmentForCameraCompat() { - return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled( - OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); - } - - private boolean isCameraCompatTreatmentActive() { - DisplayContent displayContent = mActivityRecord.mDisplayContent; - if (displayContent == null) { - return false; - } - return displayContent.mDisplayRotationCompatPolicy != null - && displayContent.mDisplayRotationCompatPolicy - .isTreatmentEnabledForActivity(mActivityRecord); + return getAppCompatCapability().shouldApplyFreeformTreatmentForCameraCompat(); } @FreeformCameraCompatMode @@ -785,16 +404,18 @@ final class LetterboxUiController { } void updateLetterboxSurfaceIfNeeded(WindowState winHint) { - updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction()); + updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction(), + mActivityRecord.getPendingTransaction()); } - void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) { + void updateLetterboxSurfaceIfNeeded(WindowState winHint, @NonNull Transaction t, + @NonNull Transaction inputT) { if (shouldNotLayoutLetterbox(winHint)) { return; } layoutLetterboxIfNeeded(winHint); if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { - mLetterbox.applySurfaceChanges(t); + mLetterbox.applySurfaceChanges(t, inputT); } } @@ -850,7 +471,8 @@ final class LetterboxUiController { // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. - final Rect innerFrame = mActivityRecord.mTransparentPolicy.isRunning() + final Rect innerFrame = mActivityRecord.mAppCompatController + .getTransparentPolicy().isRunning() ? mActivityRecord.getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); if (mDoubleTapEvent) { @@ -987,7 +609,7 @@ final class LetterboxUiController { // Don't resize to split screen size when in book mode if letterbox position is centered return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape) || isCameraCompatSplitScreenAspectRatioAllowed() - && isCameraCompatTreatmentActive(); + && getAppCompatCapability().isCameraCompatTreatmentActive(); } private float getDefaultMinAspectRatioForUnresizableApps() { @@ -1089,13 +711,7 @@ final class LetterboxUiController { * Whether we should enable users to resize the current app. */ boolean shouldEnableUserAspectRatioSettings() { - // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has - // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null, - // the current app doesn't opt-out so the first part of the predicate is true. - return !mAllowUserAspectRatioOverrideOptProp.isFalse() - && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() - && mActivityRecord.mDisplayContent != null - && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest(); + return getAppCompatCapability().shouldEnableUserAspectRatioSettings(); } /** @@ -1114,15 +730,6 @@ final class LetterboxUiController { && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN; } - boolean isUserFullscreenOverrideEnabled() { - if (mAllowUserAspectRatioOverrideOptProp.isFalse() - || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse() - || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) { - return false; - } - return true; - } - boolean shouldApplyUserFullscreenOverride() { if (isUserFullscreenOverrideEnabled()) { mUserAspectRatio = getUserMinAspectRatioOverrideCode(); @@ -1133,11 +740,12 @@ final class LetterboxUiController { return false; } + boolean isUserFullscreenOverrideEnabled() { + return getAppCompatCapability().isUserFullscreenOverrideEnabled(); + } + boolean isSystemOverrideToFullscreenEnabled() { - return mIsSystemOverrideToFullscreenEnabled - && !mAllowOrientationOverrideOptProp.isFalse() - && (mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET - || mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN); + return getAppCompatCapability().isSystemOverrideToFullscreenEnabled(mUserAspectRatio); } boolean hasFullscreenOverride() { @@ -1311,8 +919,9 @@ final class LetterboxUiController { ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds(); // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty - final Rect opaqueActivityBounds = mActivityRecord.mTransparentPolicy - .getFirstOpaqueActivity().map(ActivityRecord::getScreenResolvedBounds) + final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController + .getTransparentPolicy().getFirstOpaqueActivity() + .map(ActivityRecord::getScreenResolvedBounds) .orElse(mActivityRecord.getScreenResolvedBounds()); return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() @@ -1331,6 +940,11 @@ final class LetterboxUiController { return isHorizontalReachabilityEnabled() || isVerticalReachabilityEnabled(); } + // TODO(b/346264992): Remove after AppCompatController refactoring + private AppCompatCapability getAppCompatCapability() { + return mActivityRecord.mAppCompatController.getAppCompatCapability(); + } + /** * Whether vertical reachability is enabled for an activity in the current configuration. * @@ -1350,8 +964,9 @@ final class LetterboxUiController { ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds(); // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty. - final Rect opaqueActivityBounds = mActivityRecord.mTransparentPolicy - .getFirstOpaqueActivity().map(ActivityRecord::getScreenResolvedBounds) + final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController + .getTransparentPolicy().getFirstOpaqueActivity() + .map(ActivityRecord::getScreenResolvedBounds) .orElse(mActivityRecord.getScreenResolvedBounds()); return mLetterboxConfiguration.getIsVerticalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() @@ -1368,7 +983,8 @@ final class LetterboxUiController { @VisibleForTesting boolean shouldShowLetterboxUi(WindowState mainWindow) { - if (mIsRelaunchingAfterRequestedOrientationChanged) { + if (getAppCompatCapability().getAppCompatOrientationCapability() + .getIsRelaunchingAfterRequestedOrientationChanged()) { return mLastShouldShowLetterboxUi; } @@ -1457,7 +1073,7 @@ final class LetterboxUiController { // corners because we assume the specific layout would. This is the case when the layout // of the translucent activity uses only a part of all the bounds because of the use of // LayoutParams.WRAP_CONTENT. - if (mActivityRecord.mTransparentPolicy.isRunning() + if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth || cropBounds.height() != mainWindow.mRequestedHeight)) { return null; @@ -1528,7 +1144,8 @@ final class LetterboxUiController { } boolean getIsRelaunchingAfterRequestedOrientationChanged() { - return mIsRelaunchingAfterRequestedOrientationChanged; + return getAppCompatCapability().getAppCompatOrientationCapability() + .getIsRelaunchingAfterRequestedOrientationChanged(); } private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) { @@ -1713,13 +1330,13 @@ final class LetterboxUiController { int getLetterboxPositionForLogging() { int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION; if (isHorizontalReachabilityEnabled()) { - int letterboxPositionForHorizontalReachability = getLetterboxConfiguration() + int letterboxPositionForHorizontalReachability = mLetterboxConfiguration .getLetterboxPositionForHorizontalReachability( isDisplayFullScreenAndInPosture(/* isTabletop */ false)); positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition( letterboxPositionForHorizontalReachability); } else if (isVerticalReachabilityEnabled()) { - int letterboxPositionForVerticalReachability = getLetterboxConfiguration() + int letterboxPositionForVerticalReachability = mLetterboxConfiguration .getLetterboxPositionForVerticalReachability( isDisplayFullScreenAndInPosture(/* isTabletop */ true)); positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition( @@ -1728,10 +1345,6 @@ final class LetterboxUiController { return positionToLog; } - private LetterboxConfiguration getLetterboxConfiguration() { - return mLetterboxConfiguration; - } - /** * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}. */ @@ -1761,21 +1374,4 @@ final class LetterboxUiController { w.mAttrs.insetsFlags.appearance ); } - - @NonNull - private static BooleanSupplier asLazy(@NonNull BooleanSupplier supplier) { - return new BooleanSupplier() { - private boolean mRead; - private boolean mValue; - - @Override - public boolean getAsBoolean() { - if (!mRead) { - mRead = true; - mValue = supplier.getAsBoolean(); - } - return mValue; - } - }; - } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index f5ab38f72b54..54ba47eeb441 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1905,6 +1905,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Don't do recursive work. return; } + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RWC_ensureActivitiesVisible"); mTaskSupervisor.beginActivityVisibilityUpdate(); try { // First the front root tasks. In case any are not fullscreen and are in front of home. @@ -1914,6 +1915,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } finally { mTaskSupervisor.endActivityVisibilityUpdate(); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 3b3eeb496ab7..4a0239bc29b2 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -41,7 +41,6 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.window.flags.Flags.windowSessionRelayoutInfo; import android.annotation.NonNull; import android.annotation.Nullable; @@ -64,7 +63,6 @@ import android.os.Trace; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; -import android.util.MergedConfiguration; import android.util.Slog; import android.view.IWindow; import android.view.IWindowId; @@ -81,7 +79,6 @@ import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager; import android.view.WindowRelayoutResult; -import android.window.ClientWindowFrames; import android.window.InputTransferToken; import android.window.OnBackInvokedCallbackInfo; @@ -290,37 +287,12 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { return res; } - /** @deprecated */ - @Deprecated - @Override - public int relayoutLegacy(IWindow window, WindowManager.LayoutParams attrs, - int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq, - int lastSyncSeqId, ClientWindowFrames outFrames, - MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, - InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, - Bundle outBundle) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); - int res = mService.relayoutWindow(this, window, attrs, - requestedWidth, requestedHeight, viewFlags, flags, seq, - lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState, - outActiveControls, outBundle); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - return res; - } - @Override public void relayoutAsync(IWindow window, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq, int lastSyncSeqId) { - if (windowSessionRelayoutInfo()) { - relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq, - lastSyncSeqId, null /* outRelayoutResult */); - } else { - relayoutLegacy(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq, - lastSyncSeqId, null /* outFrames */, null /* mergedConfiguration */, - null /* outSurfaceControl */, null /* outInsetsState */, - null /* outActiveControls */, null /* outSyncIdBundle */); - } + relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq, + lastSyncSeqId, null /* outRelayoutResult */); } @Override diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 787c5d6f7699..9b8c03809878 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -47,8 +47,6 @@ import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.SurfaceControl.METADATA_TASK_ID; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -1994,6 +1992,12 @@ class Task extends TaskFragment { final boolean wasInMultiWindowMode = inMultiWindowMode(); final boolean wasInPictureInPicture = inPinnedWindowingMode(); super.onConfigurationChanged(newParentConfig); + if (mDisplayContent == null) { + // This should be initializing from Task.Builder. The onConfigurationChanged will be + // called again when this task is attached to hierarchy. Early return here because the + // following operations are no-op for a non-attached task. + return; + } // Only need to update surface size here since the super method will handle updating // surface position. updateSurfaceSize(getSyncTransaction()); @@ -3580,15 +3584,29 @@ class Task extends TaskFragment { ? null : new PictureInPictureParams(top.pictureInPictureArgs); } - Rect getDisplayCutoutInsets() { - if (mDisplayContent == null || getDisplayInfo().displayCutout == null) return null; + /** @return The display cutout insets where the main window is not allowed to extend to. */ + @NonNull Rect getDisplayCutoutInsets() { + final Rect displayCutoutInsets = new Rect(); + if (mDisplayContent == null || getDisplayInfo().displayCutout == null) { + return displayCutoutInsets; + } final WindowState w = getTopVisibleAppMainWindow(); - final int displayCutoutMode = w == null - ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT - : w.getAttrs().layoutInDisplayCutoutMode; - return (displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - || displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) - ? null : getDisplayInfo().displayCutout.getSafeInsets(); + final Rect displayFrame; + if (w != null && w.mHaveFrame) { + displayFrame = w.getDisplayFrame(); + } else { + displayFrame = mDisplayContent.getBounds(); + displayFrame.inset(getDisplayInfo().displayCutout.getSafeInsets()); + } + final Rect taskBounds = getBounds(); + if (displayCutoutInsets.setIntersect(taskBounds, displayFrame)) { + displayCutoutInsets.set( + displayCutoutInsets.left - taskBounds.left, + displayCutoutInsets.top - taskBounds.top, + taskBounds.right - displayCutoutInsets.right, + taskBounds.bottom - displayCutoutInsets.bottom); + } + return displayCutoutInsets; } /** diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index ab72e3c42d85..acdb66a49251 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -29,6 +29,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.app.WindowConfiguration.isFloating; import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -2241,15 +2242,17 @@ class TaskFragment extends WindowContainer<WindowContainer> { static class ConfigOverrideHint { @Nullable DisplayInfo mTmpOverrideDisplayInfo; @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets; - @Nullable Rect mTmpParentAppBoundsOverride; + @Nullable Rect mParentAppBoundsOverride; int mTmpOverrideConfigOrientation; boolean mUseOverrideInsetsForConfig; void resolveTmpOverrides(DisplayContent dc, Configuration parentConfig, boolean isFixedRotationTransforming) { - mTmpParentAppBoundsOverride = new Rect(parentConfig.windowConfiguration.getAppBounds()); + mParentAppBoundsOverride = new Rect(parentConfig.windowConfiguration.getAppBounds()); + mTmpOverrideConfigOrientation = parentConfig.orientation; final Insets insets; - if (mUseOverrideInsetsForConfig && dc != null) { + if (mUseOverrideInsetsForConfig && dc != null + && !isFloating(parentConfig.windowConfiguration.getWindowingMode())) { // Insets are decoupled from configuration by default from V+, use legacy // compatibility behaviour for apps targeting SDK earlier than 35 // (see applySizeOverrideIfNeeded). @@ -2269,13 +2272,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { } else { insets = Insets.NONE; } - mTmpParentAppBoundsOverride.inset(insets); + mParentAppBoundsOverride.inset(insets); } void resetTmpOverrides() { mTmpOverrideDisplayInfo = null; mTmpCompatInsets = null; - mTmpParentAppBoundsOverride = null; mTmpOverrideConfigOrientation = ORIENTATION_UNDEFINED; } } @@ -2364,7 +2366,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final Rect containingAppBounds; if (insideParentBounds) { containingAppBounds = useOverrideInsetsForConfig - ? overrideHint.mTmpParentAppBoundsOverride + ? overrideHint.mParentAppBoundsOverride : parentConfig.windowConfiguration.getAppBounds(); } else { // Restrict appBounds to display non-decor rather than parent because the diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 63ca46991c73..25721280d97e 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1903,7 +1903,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } else { final List<TransitionInfo.Change> changes = info.getChanges(); for (int i = changes.size() - 1; i >= 0; --i) { - if (mTargets.get(i).mContainer.asActivityRecord() != null) { + final WindowContainer<?> container = mTargets.get(i).mContainer; + if (container.asActivityRecord() != null + || (container.asTask() != null + && mOverrideOptions.getOverrideTaskTransition())) { changes.get(i).setAnimationOptions(mOverrideOptions); // TODO(b/295805497): Extract mBackgroundColor from AnimationOptions. changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor()); @@ -2541,9 +2544,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (wc.asWindowState() != null) continue; final ChangeInfo changeInfo = changes.get(wc); - - // Reject no-ops - if (!changeInfo.hasChanged()) { + // Reject no-ops, unless wallpaper + if (!changeInfo.hasChanged() + && (!Flags.ensureWallpaperInTransitions() || wc.asWallpaperToken() == null)) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Rejecting as no-op: %s", wc); continue; @@ -2816,6 +2819,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final Rect parentBounds = parent.getBounds(); change.setEndRelOffset(bounds.left - parentBounds.left, bounds.top - parentBounds.top); + if (Flags.activityEmbeddingOverlayPresentationFlag()) { + change.setEndParentSize(parentBounds.width(), parentBounds.height()); + } int endRotation = target.getWindowConfiguration().getRotation(); if (activityRecord != null) { // TODO(b/227427984): Shell needs to aware letterbox. diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java index b408397d1861..3044abdffa8f 100644 --- a/services/core/java/com/android/server/wm/TransparentPolicy.java +++ b/services/core/java/com/android/server/wm/TransparentPolicy.java @@ -260,8 +260,9 @@ class TransparentPolicy { private void start(@NonNull ActivityRecord firstOpaqueActivity) { mFirstOpaqueActivity = firstOpaqueActivity; - mFirstOpaqueActivity.mTransparentPolicy - .mDestroyListeners.add(mActivityRecord.mTransparentPolicy); + mFirstOpaqueActivity.mAppCompatController.getTransparentPolicy() + .mDestroyListeners.add(mActivityRecord.mAppCompatController + .getTransparentPolicy()); inheritFromOpaque(firstOpaqueActivity); final WindowContainer<?> parent = mActivityRecord.getParent(); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( @@ -312,8 +313,9 @@ class TransparentPolicy { mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mInheritedCompatDisplayInsets = null; if (mFirstOpaqueActivity != null) { - mFirstOpaqueActivity.mTransparentPolicy - .mDestroyListeners.remove(mActivityRecord.mTransparentPolicy); + mFirstOpaqueActivity.mAppCompatController.getTransparentPolicy() + .mDestroyListeners.remove(mActivityRecord.mAppCompatController + .getTransparentPolicy()); } mFirstOpaqueActivity = null; } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3e43f5a2da66..86440ac3782d 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -60,6 +60,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; @@ -764,10 +765,19 @@ class WallpaperController { void collectTopWallpapers(Transition transition) { if (mFindResults.hasTopShowWhenLockedWallpaper()) { - transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper); + if (Flags.ensureWallpaperInTransitions()) { + transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper.mToken); + } else { + transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper); + } + } if (mFindResults.hasTopHideWhenLockedWallpaper()) { - transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper); + if (Flags.ensureWallpaperInTransitions()) { + transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper.mToken); + } else { + transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper); + } } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 70143bae9fb0..1f31af68c693 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -202,8 +202,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< private int mLastLayer = 0; private SurfaceControl mLastRelativeToLayer = null; - // TODO(b/132320879): Remove this from WindowContainers except DisplayContent. - private final Transaction mPendingTransaction; + private Transaction mPendingTransaction; /** * Windows that clients are waiting to have drawn. @@ -358,7 +357,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< WindowContainer(WindowManagerService wms) { mWmService = wms; mTransitionController = mWmService.mAtmService.getTransitionController(); - mPendingTransaction = wms.mTransactionFactory.get(); mSyncTransaction = wms.mTransactionFactory.get(); mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms); mSurfaceFreezer = new SurfaceFreezer(this, wms); @@ -580,6 +578,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); + if (mParent == null) { + // Avoid unnecessary surface operation before attaching to a parent. + return; + } updateSurfacePositionNonOrganized(); scheduleAnimation(); if (mOverlayHost != null) { @@ -1074,8 +1076,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } mDisplayContent = dc; - if (dc != null && dc != this) { + if (dc != null && dc != this && mPendingTransaction != null) { dc.getPendingTransaction().merge(mPendingTransaction); + mPendingTransaction = null; } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mChildren.get(i); @@ -2922,14 +2925,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public Transaction getPendingTransaction() { - final DisplayContent displayContent = getDisplayContent(); - if (displayContent != null && displayContent != this) { - return displayContent.getPendingTransaction(); + final WindowContainer<?> dc = mDisplayContent; + if (dc != null && dc.mPendingTransaction != null) { + return dc.mPendingTransaction; } // This WindowContainer has not attached to a display yet or this is a DisplayContent, so we // let the caller to save the surface operations within the local mPendingTransaction. // If this is not a DisplayContent, we will merge it to the pending transaction of its // display once it attaches to it. + if (mPendingTransaction == null) { + mPendingTransaction = mWmService.mTransactionFactory.get(); + } return mPendingTransaction; } @@ -3980,6 +3986,19 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** + * Returns {@code true} if this window container belongs to a different sync group than the + * given group. + */ + boolean isDifferentSyncGroup(@Nullable BLASTSyncEngine.SyncGroup group) { + if (group == null) return false; + final BLASTSyncEngine.SyncGroup thisGroup = getSyncGroup(); + if (thisGroup == null || group == thisGroup) return false; + Slog.d(TAG, this + " uses a different SyncGroup, current=" + thisGroup.mSyncId + + " given=" + group.mSyncId); + return true; + } + + /** * Recursively finishes/cleans-up sync state of this subtree and collects all the sync * transactions into `outMergedTransaction`. * @param outMergedTransaction A transaction to merge all the recorded sync operations into. @@ -3988,10 +4007,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ void finishSync(Transaction outMergedTransaction, @Nullable BLASTSyncEngine.SyncGroup group, boolean cancel) { - if (mSyncState == SYNC_STATE_NONE) return; - final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup(); - // If it's null, then we need to clean-up anyways. - if (syncGroup != null && group != syncGroup) return; + if (mSyncState == SYNC_STATE_NONE) { + if (mSyncGroup != null) { + Slog.e(TAG, "finishSync: stale group " + mSyncGroup.mSyncId + " of " + this); + mSyncGroup = null; + } + return; + } + if (isDifferentSyncGroup(group)) return; ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "finishSync cancel=%b for %s", cancel, this); outMergedTransaction.merge(mSyncTransaction); for (int i = mChildren.size() - 1; i >= 0; --i) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b814ccd2babe..72ec05847973 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2027,7 +2027,9 @@ public class WindowManagerService extends IWindowManager.Stub // Otherwise, look at the package final ApplicationInfo appInfo = mPmInternal.getApplicationInfo( packageName, 0 /* flags */, SYSTEM_UID, UserHandle.getUserId(callingUid)); - if (appInfo == null || appInfo.uid != callingUid) { + if (appInfo == null + || !mPmInternal.isSameApp( + packageName, callingUid, UserHandle.getUserId(callingUid))) { throw new SecurityException("Package " + packageName + " not in UID " + callingUid); } @@ -2290,32 +2292,7 @@ public class WindowManagerService extends IWindowManager.Stub outInsetsState = null; outActiveControls = null; } - return relayoutWindowInner(session, client, attrs, requestedWidth, requestedHeight, - viewVisibility, flags, seq, lastSyncSeqId, outFrames, outMergedConfiguration, - outSurfaceControl, outInsetsState, outActiveControls, null /* outBundle */, - outRelayoutResult); - } - /** @deprecated */ - @Deprecated - public int relayoutWindow(Session session, IWindow client, LayoutParams attrs, - int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq, - int lastSyncSeqId, ClientWindowFrames outFrames, - MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl, - InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, - Bundle outBundle) { - return relayoutWindowInner(session, client, attrs, requestedWidth, requestedHeight, - viewVisibility, flags, seq, lastSyncSeqId, outFrames, outMergedConfiguration, - outSurfaceControl, outInsetsState, outActiveControls, outBundle, - null /* outRelayoutResult */); - } - - private int relayoutWindowInner(Session session, IWindow client, LayoutParams attrs, - int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq, - int lastSyncSeqId, ClientWindowFrames outFrames, - MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl, - InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, - Bundle outBundle, WindowRelayoutResult outRelayoutResult) { if (outActiveControls != null) { outActiveControls.set(null, false /* copyControls */); } @@ -2647,14 +2624,8 @@ public class WindowManagerService extends IWindowManager.Stub } if (outFrames != null && outMergedConfiguration != null) { - final boolean shouldReportActivityWindowInfo; - if (Flags.windowSessionRelayoutInfo()) { - shouldReportActivityWindowInfo = outRelayoutResult != null - && win.mLastReportedActivityWindowInfo != null; - } else { - shouldReportActivityWindowInfo = outBundle != null + final boolean shouldReportActivityWindowInfo = outRelayoutResult != null && win.mLastReportedActivityWindowInfo != null; - } final ActivityWindowInfo outActivityWindowInfo = shouldReportActivityWindowInfo ? new ActivityWindowInfo() : null; @@ -2663,13 +2634,7 @@ public class WindowManagerService extends IWindowManager.Stub outActivityWindowInfo, false /* useLatestConfig */, shouldRelayout); if (shouldReportActivityWindowInfo) { - if (Flags.windowSessionRelayoutInfo()) { - outRelayoutResult.activityWindowInfo = outActivityWindowInfo; - } else { - outBundle.putParcelable( - IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, - outActivityWindowInfo); - } + outRelayoutResult.activityWindowInfo = outActivityWindowInfo; } // Set resize-handled here because the values are sent back to the client. @@ -2700,28 +2665,16 @@ public class WindowManagerService extends IWindowManager.Stub win.isVisible() /* visible */, false /* removed */); } - if (Flags.windowSessionRelayoutInfo()) { - if (outRelayoutResult != null) { - if (win.syncNextBuffer() && viewVisibility == View.VISIBLE - && win.mSyncSeqId > lastSyncSeqId) { - outRelayoutResult.syncSeqId = win.shouldSyncWithBuffers() - ? win.mSyncSeqId - : -1; - win.markRedrawForSyncReported(); - } else { - outRelayoutResult.syncSeqId = -1; - } - } - } else if (outBundle != null) { - final int maybeSyncSeqId; + if (outRelayoutResult != null) { if (win.syncNextBuffer() && viewVisibility == View.VISIBLE && win.mSyncSeqId > lastSyncSeqId) { - maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1; + outRelayoutResult.syncSeqId = win.shouldSyncWithBuffers() + ? win.mSyncSeqId + : -1; win.markRedrawForSyncReported(); } else { - maybeSyncSeqId = -1; + outRelayoutResult.syncSeqId = -1; } - outBundle.putInt(IWindowSession.KEY_RELAYOUT_BUNDLE_SEQID, maybeSyncSeqId); } if (configChanged) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 72109d34ec8a..d26df7a67ad5 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -609,11 +609,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub int effects = TRANSACT_EFFECTS_NONE; ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId); mService.deferWindowLayout(); + mService.mTaskSupervisor.beginDeferResume(); + boolean deferResume = true; mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */); - final boolean shouldDeferTransitionReady = transition != null && !t.isEmpty() - && (transition.isCollecting() || Flags.alwaysDeferTransitionWhenApplyWct()); - if (shouldDeferTransitionReady) { - transition.deferTransitionReady(); + boolean deferTransitionReady = false; + if (transition != null && !t.isEmpty()) { + if (transition.isCollecting()) { + deferTransitionReady = true; + transition.deferTransitionReady(); + } else if (Flags.alwaysDeferTransitionWhenApplyWct()) { + Slog.w(TAG, "Transition is not collecting when applyTransaction." + + " transition=" + transition + " state=" + transition.getState()); + transition = null; + } } try { final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>(); @@ -750,6 +758,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); + mService.mTaskSupervisor.endDeferResume(); + deferResume = false; // Already calls ensureActivityConfig mService.mRootWindowContainer.ensureActivitiesVisible(); mService.mRootWindowContainer.resumeFocusedTasksTopActivities(); @@ -767,10 +777,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.mWindowManager.mWindowPlacerLocked.requestTraversal(); } } finally { - if (shouldDeferTransitionReady) { + if (deferTransitionReady) { transition.continueTransitionReady(); } mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); + if (deferResume) { + mService.mTaskSupervisor.endDeferResume(); + } mService.continueWindowLayout(); } return effects; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index dcd4bd68c3fc..9d4a3b8b7f1c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -96,6 +96,7 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; +import static android.util.SequenceUtils.getNextSeq; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; @@ -3652,6 +3653,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } outFrames.compatScale = getCompatScaleForClient(); + outFrames.seq = getNextSeq(mLastReportedFrames.seq); if (mLastReportedFrames != outFrames) { mLastReportedFrames.setTo(outFrames); } @@ -3682,7 +3684,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void fillInsetsState(@NonNull InsetsState outInsetsState, boolean copySources) { + final int lastSeq = mLastReportedInsetsState.getSeq(); outInsetsState.set(getCompatInsetsState(), copySources); + outInsetsState.setSeq(getNextSeq(lastSeq)); if (outInsetsState != mLastReportedInsetsState) { // No need to copy for the recorded. mLastReportedInsetsState.set(outInsetsState, false /* copySources */); @@ -3691,9 +3695,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void fillInsetsSourceControls(@NonNull InsetsSourceControl.Array outArray, boolean copyControls) { + final int lastSeq = mLastReportedInsetsState.getSeq(); final InsetsSourceControl[] controls = getDisplayContent().getInsetsStateController().getControlsForDispatch(this); outArray.set(controls, copyControls); + outArray.setSeq(getNextSeq(lastSeq)); if (outArray != mLastReportedActiveControls) { // No need to copy for the recorded. mLastReportedActiveControls.setTo(outArray, false /* copyControls */); @@ -5791,8 +5797,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group, boolean cancel) { - final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup(); - if (syncGroup != null && group != syncGroup) return; + if (isDifferentSyncGroup(group)) return; mPrepareSyncSeqId = 0; if (cancel) { // This is leaving sync so any buffers left in the sync have a chance of diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml index 2ccd1e4c00c7..3b81f0a6191e 100644 --- a/services/core/lint-baseline.xml +++ b/services/core/lint-baseline.xml @@ -145,4 +145,37 @@ line="7158"/> </issue> + <issue + id="FlaggedApi" + message="Method `recordSmartReplied()` is a flagged API and should be inside an `if (Flags.lifetimeExtensionRefactor())` check (or annotate the surrounding method `onNotificationSmartReplySent` with `@FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) to transfer requirement to caller`)" + errorLine1=" r.recordSmartReplied();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java" + line="1591" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPackageImportanceWithIdentity()` is a flagged API and should be inside an `if (Flags.lifetimeExtensionRefactor())` check (or annotate the surrounding method `enqueueNotificationInternal` with `@FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) to transfer requirement to caller`)" + errorLine1=" final int packageImportance = getPackageImportanceWithIdentity(pkg);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java" + line="7546" + column="39"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `getPackageImportanceWithIdentity()` is a flagged API and should be inside an `if (Flags.lifetimeExtensionRefactor())` check (or annotate the surrounding method `onShortcutRemoved` with `@FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) to transfer requirement to caller`)" + errorLine1=" final int packageImportance = getPackageImportanceWithIdentity(packageName);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java" + line="7916" + column="51"/> + </issue> + </issues>
\ No newline at end of file diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp index da965bb02460..32b571a8ee69 100644 --- a/services/devicepolicy/Android.bp +++ b/services/devicepolicy/Android.bp @@ -17,8 +17,10 @@ filegroup { java_library_static { name: "services.devicepolicy", defaults: ["platform_service_defaults"], - srcs: [":services.devicepolicy-sources"], - + srcs: [ + ":services.devicepolicy-sources", + ":statslog-devicepolicy-java-gen", + ], libs: [ "services.core", "app-compat-annotations", @@ -27,3 +29,11 @@ java_library_static { "androidx.annotation_annotation", ], } + +genrule { + name: "statslog-devicepolicy-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module devicepolicy" + + " --javaPackage com.android.server.devicepolicy --javaClass DevicePolicyStatsLog", + out: ["com/android/server/devicepolicy/DevicePolicyStatsLog.java"], +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e8204e0434c3..e122fe039c45 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -109,6 +109,7 @@ import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTI import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION; import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND; import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND; +import static android.app.StatsManager.PULL_SUCCESS; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; @@ -265,12 +266,26 @@ import static android.provider.Telephony.Carriers.INVALID_APN_ID; import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS; import static com.android.server.devicepolicy.DevicePolicyEngine.DEFAULT_POLICY_SIZE_LIMIT; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__COPE; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__DEVICE_OWNER; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__DEVICE_OWNER_FINANCED; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__MANAGEMENT_MODE_UNSPECIFIED; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__PROFILE_OWNER; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_STATE; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_HIGH; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_LEGACY; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_LOW; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_MEDIUM; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_NONE; +import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_UNSPECIFIED; import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; @@ -305,6 +320,7 @@ import android.app.IServiceConnection; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.StatsManager; import android.app.StatusBarManager; import android.app.admin.AccountTypePolicyKey; import android.app.admin.BooleanPolicyValue; @@ -477,6 +493,7 @@ import android.util.IntArray; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.StatsEvent; import android.util.Xml; import android.view.IWindowManager; import android.view.accessibility.AccessibilityManager; @@ -3348,6 +3365,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { mDevicePolicyEngine.reapplyAllPoliciesOnBootLocked(); } + if (Flags.managementModePolicyMetrics()) { + registerStatsCallbacks(); + } break; case SystemService.PHASE_ACTIVITY_MANAGER_READY: synchronized (getLockObject()) { @@ -3487,6 +3507,121 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return true; } + /** Register callbacks for statsd pulled atoms. */ + private void registerStatsCallbacks() { + final StatsManager statsManager = mContext.getSystemService(StatsManager.class); + if (statsManager == null) { + Slog.wtf(LOG_TAG, "StatsManager system service not found."); + return; + } + statsManager.setPullAtomCallback( + DEVICE_POLICY_MANAGEMENT_MODE, + null, // use defaultPullAtomMetadata values + DIRECT_EXECUTOR, + this::onPullManagementModeAtom); + statsManager.setPullAtomCallback( + DEVICE_POLICY_STATE, + null, // use defaultPullAtomMetadata values + DIRECT_EXECUTOR, + this::onPullPolicyStateAtom); + } + + /** Writes the pulled atoms. */ + private int onPullManagementModeAtom(int atomTag, List<StatsEvent> statsEvents) { + synchronized (getLockObject()) { + statsEvents.add(DevicePolicyStatsLog.buildStatsEvent( + DEVICE_POLICY_MANAGEMENT_MODE, + getStatsManagementModeLocked().managementMode())); + return PULL_SUCCESS; + } + } + + /** Writes the pulled atoms. */ + private int onPullPolicyStateAtom(int atomTag, List<StatsEvent> statsEvents) { + synchronized (getLockObject()) { + StatsManagementMode statsManagementMode = getStatsManagementModeLocked(); + if (statsManagementMode.admin() != null) { + statsEvents.add(DevicePolicyStatsLog.buildStatsEvent(DEVICE_POLICY_STATE, + getRequiredPasswordComplexityStatsLocked(statsManagementMode.admin()), + statsManagementMode.managementMode() + )); + } else { + statsEvents.add(DevicePolicyStatsLog.buildStatsEvent(DEVICE_POLICY_STATE, + DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_NONE, + DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__MANAGEMENT_MODE_UNSPECIFIED + )); + } + return PULL_SUCCESS; + } + } + + private StatsManagementMode getStatsManagementModeLocked() { + int managementMode = + DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__MANAGEMENT_MODE_UNSPECIFIED; + ActiveAdmin admin = getDeviceOwnerAdminLocked(); + if (admin != null) { + managementMode = getDeviceOwnerTypeLocked( + getDeviceOwnerComponent(false).getPackageName()) + != DEVICE_OWNER_TYPE_FINANCED + ? DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__DEVICE_OWNER + : DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__DEVICE_OWNER_FINANCED; + } else { + // Find the first user with managing_app. + for (Integer profileUserId : mOwners.getProfileOwnerKeys()) { + if (isManagedProfile(profileUserId)) { + admin = getProfileOwnerAdminLocked(profileUserId); + managementMode = mOwners.isProfileOwnerOfOrganizationOwnedDevice( + profileUserId) + ? DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__COPE + : DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__PROFILE_OWNER; + break; + } + } + } + return new StatsManagementMode(managementMode, admin); + } + + private record StatsManagementMode(int managementMode, ActiveAdmin admin) { + } + + @GuardedBy("getLockObject()") + private int getRequiredPasswordComplexityStatsLocked(ActiveAdmin admin) { + int userId = admin.getUserHandle().getIdentifier(); + EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin( + admin.info.getComponent(), + userId, + admin); + + Integer passwordComplexity = mDevicePolicyEngine.getLocalPolicySetByAdmin( + PolicyDefinition.PASSWORD_COMPLEXITY, + enforcingAdmin, + userId); + if (passwordComplexity == null) { + return admin.mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED + ? DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_LEGACY + : DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_UNSPECIFIED; + } + switch (passwordComplexity) { + case PASSWORD_COMPLEXITY_NONE -> { + return DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_NONE; + } + case PASSWORD_COMPLEXITY_LOW -> { + return DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_LOW; + } + case PASSWORD_COMPLEXITY_MEDIUM -> { + return DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_MEDIUM; + } + case PASSWORD_COMPLEXITY_HIGH -> { + return DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_HIGH; + } + default -> { + Slogf.wtf(LOG_TAG, "Unhandled password complexity: " + passwordComplexity); + // The following line is unreachable as Slogf.wtf crashes the process. + // But we need this to avoid compilation error missing return statement. + return DEVICE_POLICY_STATE__PASSWORD_COMPLEXITY__COMPLEXITY_UNSPECIFIED; + } + } + } private void applyManagedSubscriptionsPolicyIfRequired() { int copeProfileUserId = getOrganizationOwnedProfileUserId(); @@ -4115,6 +4250,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void clearOrgOwnedProfileOwnerUserRestrictions(UserHandle parentUserHandle) { mUserManager.setUserRestriction( UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false, parentUserHandle); + if (mInjector.userManagerIsHeadlessSystemUserMode()) { + mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, + false, UserHandle.SYSTEM); + } mUserManager.setUserRestriction( UserManager.DISALLOW_ADD_USER, false, parentUserHandle); } @@ -17874,6 +18013,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, isProfileOwnerOnOrganizationOwnedDevice, parentUser); + if (mInjector.userManagerIsHeadlessSystemUserMode()) { + // For HSUM, additionally set this on user 0 to block ADB from removing the profile. + mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, + isProfileOwnerOnOrganizationOwnedDevice, + UserHandle.SYSTEM); + } mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, isProfileOwnerOnOrganizationOwnedDevice, parentUser); diff --git a/services/fakes/Android.bp b/services/fakes/Android.bp index 148054b31e89..d44bb5ae302c 100644 --- a/services/fakes/Android.bp +++ b/services/fakes/Android.bp @@ -16,5 +16,5 @@ filegroup { "java/**/*.java", ], path: "java", - visibility: ["//frameworks/base"], + visibility: ["//frameworks/base/ravenwood:__subpackages__"], } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 107c2947c550..215cf2c5c85c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1924,7 +1924,11 @@ public final class SystemServer implements Dumpable { startRotationResolverService(context, t); startSystemCaptionsManagerService(context, t); startTextToSpeechManagerService(context, t); - startWearableSensingService(t); + if (!isWatch || !android.server.Flags.removeWearableSensingServiceFromWear()) { + startWearableSensingService(t); + } else { + Slog.d(TAG, "Not starting WearableSensingService"); + } startOnDeviceIntelligenceService(t); if (deviceHasConfigString( @@ -2640,9 +2644,13 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(MediaMetricsManagerService.class); t.traceEnd(); - t.traceBegin("StartBackgroundInstallControlService"); - mSystemServiceManager.startService(BackgroundInstallControlService.class); - t.traceEnd(); + if (!com.android.server.flags.Flags.optionalBackgroundInstallControl() + || SystemProperties.getBoolean( + "ro.system_settings.service.backgound_install_control_enabled", true)) { + t.traceBegin("StartBackgroundInstallControlService"); + mSystemServiceManager.startService(BackgroundInstallControlService.class); + t.traceEnd(); + } } t.traceBegin("StartMediaProjectionManager"); diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig index 38354e849129..e8aa68cf1a63 100644 --- a/services/java/com/android/server/flags.aconfig +++ b/services/java/com/android/server/flags.aconfig @@ -14,4 +14,11 @@ flag { namespace: "wear_frameworks" description: "Remove TextServiceManagerService on Wear" bug: "323720705" +} + +flag { + name: "remove_wearable_sensing_service_from_wear" + namespace: "wear_frameworks" + description: "Remove WearableSensingManagerService on Wear" + bug: "340929916" }
\ No newline at end of file diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp index 0da17e126d7d..3bce9b54e320 100644 --- a/services/tests/InputMethodSystemServerTests/Android.bp +++ b/services/tests/InputMethodSystemServerTests/Android.bp @@ -84,7 +84,6 @@ android_ravenwood_test { ], srcs: [ "src/com/android/server/inputmethod/**/ClientControllerTest.java", - "src/com/android/server/inputmethod/**/UserDataRepositoryTest.java", ], auto_gen_config: true, } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index a4ca317ce914..d4adba209cd9 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -87,7 +87,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe synchronized (ImfLock.class) { mVisibilityApplier.performShowIme(new Binder() /* showInputToken */, ImeTracker.Token.empty(), 0 /* showFlags */, null /* resultReceiver */, - SHOW_SOFT_INPUT); + SHOW_SOFT_INPUT, mUserId); } verifyShowSoftInput(false, true, 0 /* showFlags */); } @@ -96,7 +96,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe public void testPerformHideIme() throws Exception { synchronized (ImfLock.class) { mVisibilityApplier.performHideIme(new Binder() /* hideInputToken */, - ImeTracker.Token.empty(), null /* resultReceiver */, HIDE_SOFT_INPUT); + ImeTracker.Token.empty(), null /* resultReceiver */, HIDE_SOFT_INPUT, mUserId); } verifyHideSoftInput(false, true); } @@ -186,7 +186,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe @Test public void testShowImeScreenshot() { synchronized (ImfLock.class) { - mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY); + mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY, mUserId); } verify(mMockImeTargetVisibilityPolicy).showImeScreenshot(eq(mWindowToken), @@ -196,7 +196,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe @Test public void testRemoveImeScreenshot() { synchronized (ImfLock.class) { - mVisibilityApplier.removeImeScreenshot(Display.DEFAULT_DISPLAY); + mVisibilityApplier.removeImeScreenshot(Display.DEFAULT_DISPLAY, mUserId); } verify(mMockImeTargetVisibilityPolicy).removeImeScreenshot(eq(Display.DEFAULT_DISPLAY)); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index fbe384a62658..e81cf9df6660 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -18,6 +18,7 @@ package com.android.server.inputmethod; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import android.content.ComponentName; @@ -28,60 +29,63 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; import org.junit.Test; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public final class InputMethodSubtypeSwitchingControllerTest { - private static final String DUMMY_PACKAGE_NAME = "dummy package name"; - private static final String DUMMY_IME_LABEL = "dummy ime label"; - private static final String DUMMY_SETTING_ACTIVITY_NAME = ""; - private static final boolean DUMMY_IS_AUX_IME = false; - private static final boolean DUMMY_FORCE_DEFAULT = false; - private static final boolean DUMMY_IS_VR_IME = false; - private static final int DUMMY_IS_DEFAULT_RES_ID = 0; + private static final String TEST_PACKAGE_NAME = "test package name"; + private static final String TEST_IME_LABEL = "test ime label"; + private static final String TEST_SETTING_ACTIVITY_NAME = ""; + private static final boolean TEST_IS_AUX_IME = false; + private static final boolean TEST_FORCE_DEFAULT = false; + private static final boolean TEST_IS_VR_IME = false; + private static final int TEST_IS_DEFAULT_RES_ID = 0; private static final String SYSTEM_LOCALE = "en_US"; private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; - private static InputMethodSubtype createDummySubtype(final String locale) { - final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); - return builder.setSubtypeNameResId(0) + @NonNull + private static InputMethodSubtype createTestSubtype(@NonNull String locale) { + return new InputMethodSubtypeBuilder() + .setSubtypeNameResId(0) .setSubtypeIconResId(0) .setSubtypeLocale(locale) .setIsAsciiCapable(true) .build(); } - private static void addDummyImeSubtypeListItems(List<ImeSubtypeListItem> items, - String imeName, String imeLabel, List<String> subtypeLocales, - boolean supportsSwitchingToNextInputMethod) { - final ResolveInfo ri = new ResolveInfo(); - final ServiceInfo si = new ServiceInfo(); + private static void addTestImeSubtypeListItems(@NonNull List<ImeSubtypeListItem> items, + @NonNull String imeName, @NonNull String imeLabel, + @Nullable List<String> subtypeLocales, boolean supportsSwitchingToNextInputMethod) { final ApplicationInfo ai = new ApplicationInfo(); - ai.packageName = DUMMY_PACKAGE_NAME; + ai.packageName = TEST_PACKAGE_NAME; ai.enabled = true; + final ServiceInfo si = new ServiceInfo(); si.applicationInfo = ai; si.enabled = true; - si.packageName = DUMMY_PACKAGE_NAME; + si.packageName = TEST_PACKAGE_NAME; si.name = imeName; si.exported = true; si.nonLocalizedLabel = imeLabel; + final ResolveInfo ri = new ResolveInfo(); ri.serviceInfo = si; List<InputMethodSubtype> subtypes = null; if (subtypeLocales != null) { - subtypes = new ArrayList<InputMethodSubtype>(); + subtypes = new ArrayList<>(); for (String subtypeLocale : subtypeLocales) { - subtypes.add(createDummySubtype(subtypeLocale)); + subtypes.add(createTestSubtype(subtypeLocale)); } } - final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, - DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, - DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, DUMMY_IS_VR_IME); + final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME, + TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID, + TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME); if (subtypes == null) { items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE)); @@ -94,105 +98,104 @@ public final class InputMethodSubtypeSwitchingControllerTest { } } - private static ImeSubtypeListItem createDummyItem(ComponentName imeComponentName, - String imeName, String subtypeName, String subtypeLocale, int subtypeIndex, - String systemLocale) { - final ResolveInfo ri = new ResolveInfo(); - final ServiceInfo si = new ServiceInfo(); - final ApplicationInfo ai = new ApplicationInfo(); + @NonNull + private static ImeSubtypeListItem createTestItem(@NonNull ComponentName imeComponentName, + @NonNull String imeName, @NonNull String subtypeName, + @NonNull String subtypeLocale, int subtypeIndex) { + final var ai = new ApplicationInfo(); ai.packageName = imeComponentName.getPackageName(); ai.enabled = true; + final var si = new ServiceInfo(); si.applicationInfo = ai; si.enabled = true; si.packageName = imeComponentName.getPackageName(); si.name = imeComponentName.getClassName(); si.exported = true; - si.nonLocalizedLabel = DUMMY_IME_LABEL; + si.nonLocalizedLabel = TEST_IME_LABEL; + final var ri = new ResolveInfo(); ri.serviceInfo = si; - ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); + final var subtypes = new ArrayList<InputMethodSubtype>(); subtypes.add(new InputMethodSubtypeBuilder() .setSubtypeNameResId(0) .setSubtypeIconResId(0) .setSubtypeLocale(subtypeLocale) .setIsAsciiCapable(true) .build()); - final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, - DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, - DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, - DUMMY_IS_VR_IME); + final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME, + TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID, + TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME); return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale, - systemLocale); + SYSTEM_LOCALE); } + @NonNull private static List<ImeSubtypeListItem> createEnabledImeSubtypes() { - final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); - addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"), + final var items = new ArrayList<ImeSubtypeListItem>(); + addTestImeSubtypeListItems(items, "LatinIme", "LatinIme", List.of("en_US", "fr"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme", - Arrays.asList("en_UK", "hi"), - false /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null, + addTestImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme", + List.of("en_UK", "hi"), false /* supportsSwitchingToNextInputMethod*/); + addTestImeSubtypeListItems(items, "subtypeAwareIme", "subtypeAwareIme", null, + true /* supportsSwitchingToNextInputMethod */); + addTestImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"), + addTestImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", List.of("ja_JP"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme", - Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/); + addTestImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme", + List.of("ja_JP"), false /* supportsSwitchingToNextInputMethod*/); return items; } + @NonNull private static List<ImeSubtypeListItem> createDisabledImeSubtypes() { - final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); - addDummyImeSubtypeListItems(items, + final var items = new ArrayList<ImeSubtypeListItem>(); + addTestImeSubtypeListItems(items, "UnknownIme", "UnknownIme", - Arrays.asList("en_US", "hi"), + List.of("en_US", "hi"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, + addTestImeSubtypeListItems(items, "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme", - Arrays.asList("en_US"), + List.of("en_US"), false /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme", + addTestImeSubtypeListItems(items, "UnknownSubtypeUnawareIme", "UnknownSubtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); return items; } - private void assertNextInputMethod(final ControllerImpl controller, - final boolean onlyCurrentIme, - final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) { + private void assertNextInputMethod(@NonNull ControllerImpl controller, boolean onlyCurrentIme, + @NonNull ImeSubtypeListItem currentItem, @Nullable ImeSubtypeListItem nextItem) { InputMethodSubtype subtype = null; if (currentItem.mSubtypeName != null) { - subtype = createDummySubtype(currentItem.mSubtypeName.toString()); + subtype = createTestSubtype(currentItem.mSubtypeName.toString()); } final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme, currentItem.mImi, subtype); assertEquals(nextItem, nextIme); } - private void assertRotationOrder(final ControllerImpl controller, - final boolean onlyCurrentIme, - final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { + private void assertRotationOrder(@NonNull ControllerImpl controller, boolean onlyCurrentIme, + ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { final int numItems = expectedRotationOrderOfImeSubtypeList.length; for (int i = 0; i < numItems; i++) { - final int currentIndex = i; - final int nextIndex = (currentIndex + 1) % numItems; - final ImeSubtypeListItem currentItem = - expectedRotationOrderOfImeSubtypeList[currentIndex]; + final int nextIndex = (i + 1) % numItems; + final ImeSubtypeListItem currentItem = expectedRotationOrderOfImeSubtypeList[i]; final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex]; assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem); } } - private void onUserAction(final ControllerImpl controller, - final ImeSubtypeListItem subtypeListItem) { + private void onUserAction(@NonNull ControllerImpl controller, + @NonNull ImeSubtypeListItem subtypeListItem) { InputMethodSubtype subtype = null; if (subtypeListItem.mSubtypeName != null) { - subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString()); + subtype = createTestSubtype(subtypeListItem.mSubtypeName.toString()); } controller.onUserActionLocked(subtypeListItem.mImi, subtype); } @Test - public void testControllerImpl() throws Exception { + public void testControllerImpl() { final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); final ImeSubtypeListItem disabledIme_en_us = disabledItems.get(0); final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1); @@ -204,16 +207,17 @@ public final class InputMethodSubtypeSwitchingControllerTest { final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); final ImeSubtypeListItem switchingUnawareLatinIme_en_uk = enabledItems.get(2); final ImeSubtypeListItem switchingUnawareLatinIme_hi = enabledItems.get(3); - final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); - final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5); - final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6); + final ImeSubtypeListItem subtypeAwareIme = enabledItems.get(4); + final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(5); + final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(6); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7); final ControllerImpl controller = ControllerImpl.createFrom( null /* currentInstance */, enabledItems); // switching-aware loop assertRotationOrder(controller, false /* onlyCurrentIme */, - latinIme_en_us, latinIme_fr, japaneseIme_ja_jp); + latinIme_en_us, latinIme_fr, subtypeAwareIme, japaneseIme_ja_jp); // switching-unaware loop assertRotationOrder(controller, false /* onlyCurrentIme */, @@ -226,6 +230,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { assertRotationOrder(controller, true /* onlyCurrentIme */, switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi); assertNextInputMethod(controller, true /* onlyCurrentIme */, + subtypeAwareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, subtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, japaneseIme_ja_jp, null); @@ -252,59 +258,60 @@ public final class InputMethodSubtypeSwitchingControllerTest { } @Test - public void testControllerImplWithUserAction() throws Exception { + public void testControllerImplWithUserAction() { final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); final ImeSubtypeListItem latinIme_en_us = enabledItems.get(0); final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); - final ImeSubtypeListItem switchingUnawarelatinIme_en_uk = enabledItems.get(2); - final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); - final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); - final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5); - final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6); + final ImeSubtypeListItem switchingUnawareLatinIme_en_uk = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawareLatinIme_hi = enabledItems.get(3); + final ImeSubtypeListItem subtypeAwareIme = enabledItems.get(4); + final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(5); + final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(6); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7); final ControllerImpl controller = ControllerImpl.createFrom( null /* currentInstance */, enabledItems); // === switching-aware loop === assertRotationOrder(controller, false /* onlyCurrentIme */, - latinIme_en_us, latinIme_fr, japaneseIme_ja_jp); + latinIme_en_us, latinIme_fr, subtypeAwareIme, japaneseIme_ja_jp); // Then notify that a user did something for latinIme_fr. onUserAction(controller, latinIme_fr); assertRotationOrder(controller, false /* onlyCurrentIme */, - latinIme_fr, latinIme_en_us, japaneseIme_ja_jp); + latinIme_fr, latinIme_en_us, subtypeAwareIme, japaneseIme_ja_jp); // Then notify that a user did something for latinIme_fr again. onUserAction(controller, latinIme_fr); assertRotationOrder(controller, false /* onlyCurrentIme */, - latinIme_fr, latinIme_en_us, japaneseIme_ja_jp); - // Then notify that a user did something for japaneseIme_ja_JP. - onUserAction(controller, latinIme_fr); + latinIme_fr, latinIme_en_us, subtypeAwareIme, japaneseIme_ja_jp); + // Then notify that a user did something for subtypeAwareIme. + onUserAction(controller, subtypeAwareIme); assertRotationOrder(controller, false /* onlyCurrentIme */, - japaneseIme_ja_jp, latinIme_fr, latinIme_en_us); + subtypeAwareIme, latinIme_fr, latinIme_en_us, japaneseIme_ja_jp); // Check onlyCurrentIme == true. - assertNextInputMethod(controller, true /* onlyCurrentIme */, - japaneseIme_ja_jp, null); assertRotationOrder(controller, true /* onlyCurrentIme */, latinIme_fr, latinIme_en_us); - assertRotationOrder(controller, true /* onlyCurrentIme */, - latinIme_en_us, latinIme_fr); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + subtypeAwareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + japaneseIme_ja_jp, null); // === switching-unaware loop === assertRotationOrder(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme, switchUnawareJapaneseIme_ja_jp); // User action should be ignored for switching unaware IMEs. - onUserAction(controller, switchingUnawarelatinIme_hi); + onUserAction(controller, switchingUnawareLatinIme_hi); assertRotationOrder(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme, switchUnawareJapaneseIme_ja_jp); // User action should be ignored for switching unaware IMEs. - onUserAction(controller, switchUnawareJapaneseIme_ja_jp); + onUserAction(controller, subtypeUnawareIme); assertRotationOrder(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme, switchUnawareJapaneseIme_ja_jp); // Check onlyCurrentIme == true. assertRotationOrder(controller, true /* onlyCurrentIme */, - switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi); + switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi); assertNextInputMethod(controller, true /* onlyCurrentIme */, subtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, @@ -315,40 +322,41 @@ public final class InputMethodSubtypeSwitchingControllerTest { final ControllerImpl newController = ControllerImpl.createFrom(controller, sameEnabledItems); assertRotationOrder(newController, false /* onlyCurrentIme */, - japaneseIme_ja_jp, latinIme_fr, latinIme_en_us); + subtypeAwareIme, latinIme_fr, latinIme_en_us, japaneseIme_ja_jp); assertRotationOrder(newController, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme, switchUnawareJapaneseIme_ja_jp); // Rotation order should be initialized when created with a different subtype list. - final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList( - latinIme_en_us, latinIme_fr, switchingUnawarelatinIme_en_uk, - switchUnawareJapaneseIme_ja_jp); + final List<ImeSubtypeListItem> differentEnabledItems = List.of( + latinIme_en_us, latinIme_fr, subtypeAwareIme, switchingUnawareLatinIme_en_uk, + switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme); final ControllerImpl anotherController = ControllerImpl.createFrom(controller, differentEnabledItems); assertRotationOrder(anotherController, false /* onlyCurrentIme */, - latinIme_en_us, latinIme_fr); + latinIme_en_us, latinIme_fr, subtypeAwareIme); assertRotationOrder(anotherController, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_uk, switchUnawareJapaneseIme_ja_jp); + switchingUnawareLatinIme_en_uk, switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme); } @Test - public void testImeSubtypeListItem() throws Exception { - final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); - addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", - Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"), + public void testImeSubtypeListItem() { + final var items = new ArrayList<ImeSubtypeListItem>(); + addTestImeSubtypeListItems(items, "LatinIme", "LatinIme", + List.of("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"), true /* supportsSwitchingToNextInputMethod*/); final ImeSubtypeListItem item_en_us = items.get(0); final ImeSubtypeListItem item_fr = items.get(1); final ImeSubtypeListItem item_en = items.get(2); - final ImeSubtypeListItem item_enn = items.get(3); - final ImeSubtypeListItem item_e = items.get(4); - final ImeSubtypeListItem item_en_us_allcaps = items.get(5); + final ImeSubtypeListItem item_en_uk = items.get(3); + final ImeSubtypeListItem item_enn = items.get(4); + final ImeSubtypeListItem item_e = items.get(5); + final ImeSubtypeListItem item_en_us_allcaps = items.get(6); assertTrue(item_en_us.mIsSystemLocale); assertFalse(item_fr.mIsSystemLocale); assertFalse(item_en.mIsSystemLocale); - assertFalse(item_en.mIsSystemLocale); + assertFalse(item_en_uk.mIsSystemLocale); assertFalse(item_enn.mIsSystemLocale); assertFalse(item_e.mIsSystemLocale); assertFalse(item_en_us_allcaps.mIsSystemLocale); @@ -356,80 +364,81 @@ public final class InputMethodSubtypeSwitchingControllerTest { assertTrue(item_en_us.mIsSystemLanguage); assertFalse(item_fr.mIsSystemLanguage); assertTrue(item_en.mIsSystemLanguage); - assertFalse(item_enn.mIsSystemLocale); - assertFalse(item_e.mIsSystemLocale); - assertFalse(item_en_us_allcaps.mIsSystemLocale); + assertTrue(item_en_uk.mIsSystemLanguage); + assertFalse(item_enn.mIsSystemLanguage); + assertFalse(item_e.mIsSystemLanguage); + assertFalse(item_en_us_allcaps.mIsSystemLanguage); } @SuppressWarnings("SelfComparison") @Test - public void testImeSubtypeListComparator() throws Exception { + public void testImeSubtypeListComparator() { final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1"); final ComponentName imeX2 = new ComponentName("com.example.imeX", "Ime2"); final ComponentName imeY1 = new ComponentName("com.example.imeY", "Ime1"); final ComponentName imeZ1 = new ComponentName("com.example.imeZ", "Ime1"); { - final List<ImeSubtypeListItem> items = Arrays.asList( + final List<ImeSubtypeListItem> items = List.of( // Subtypes of two IMEs that have the same display name "X". // Subtypes that has the same locale of the system's. - createDummyItem(imeX1, "X", "E", "en_US", 0, "en_US"), - createDummyItem(imeX2, "X", "E", "en_US", 0, "en_US"), - createDummyItem(imeX1, "X", "Z", "en_US", 3, "en_US"), - createDummyItem(imeX2, "X", "Z", "en_US", 3, "en_US"), - createDummyItem(imeX1, "X", "", "en_US", 6, "en_US"), - createDummyItem(imeX2, "X", "", "en_US", 6, "en_US"), + createTestItem(imeX1, "X", "E", "en_US", 0), + createTestItem(imeX2, "X", "E", "en_US", 0), + createTestItem(imeX1, "X", "Z", "en_US", 3), + createTestItem(imeX2, "X", "Z", "en_US", 3), + createTestItem(imeX1, "X", "", "en_US", 6), + createTestItem(imeX2, "X", "", "en_US", 6), // Subtypes that has the same language of the system's. - createDummyItem(imeX1, "X", "E", "en", 1, "en_US"), - createDummyItem(imeX2, "X", "E", "en", 1, "en_US"), - createDummyItem(imeX1, "X", "Z", "en", 4, "en_US"), - createDummyItem(imeX2, "X", "Z", "en", 4, "en_US"), - createDummyItem(imeX1, "X", "", "en", 7, "en_US"), - createDummyItem(imeX2, "X", "", "en", 7, "en_US"), + createTestItem(imeX1, "X", "E", "en", 1), + createTestItem(imeX2, "X", "E", "en", 1), + createTestItem(imeX1, "X", "Z", "en", 4), + createTestItem(imeX2, "X", "Z", "en", 4), + createTestItem(imeX1, "X", "", "en", 7), + createTestItem(imeX2, "X", "", "en", 7), // Subtypes that has different language than the system's. - createDummyItem(imeX1, "X", "A", "hi_IN", 27, "en_US"), - createDummyItem(imeX2, "X", "A", "hi_IN", 27, "en_US"), - createDummyItem(imeX1, "X", "E", "ja", 2, "en_US"), - createDummyItem(imeX2, "X", "E", "ja", 2, "en_US"), - createDummyItem(imeX1, "X", "Z", "ja", 5, "en_US"), - createDummyItem(imeX2, "X", "Z", "ja", 5, "en_US"), - createDummyItem(imeX1, "X", "", "ja", 8, "en_US"), - createDummyItem(imeX2, "X", "", "ja", 8, "en_US"), + createTestItem(imeX1, "X", "A", "hi_IN", 27), + createTestItem(imeX2, "X", "A", "hi_IN", 27), + createTestItem(imeX1, "X", "E", "ja", 2), + createTestItem(imeX2, "X", "E", "ja", 2), + createTestItem(imeX1, "X", "Z", "ja", 5), + createTestItem(imeX2, "X", "Z", "ja", 5), + createTestItem(imeX1, "X", "", "ja", 8), + createTestItem(imeX2, "X", "", "ja", 8), // Subtypes of IME "Y". // Subtypes that has the same locale of the system's. - createDummyItem(imeY1, "Y", "E", "en_US", 9, "en_US"), - createDummyItem(imeY1, "Y", "Z", "en_US", 12, "en_US"), - createDummyItem(imeY1, "Y", "", "en_US", 15, "en_US"), + createTestItem(imeY1, "Y", "E", "en_US", 9), + createTestItem(imeY1, "Y", "Z", "en_US", 12), + createTestItem(imeY1, "Y", "", "en_US", 15), // Subtypes that has the same language of the system's. - createDummyItem(imeY1, "Y", "E", "en", 10, "en_US"), - createDummyItem(imeY1, "Y", "Z", "en", 13, "en_US"), - createDummyItem(imeY1, "Y", "", "en", 16, "en_US"), + createTestItem(imeY1, "Y", "E", "en", 10), + createTestItem(imeY1, "Y", "Z", "en", 13), + createTestItem(imeY1, "Y", "", "en", 16), // Subtypes that has different language than the system's. - createDummyItem(imeY1, "Y", "A", "hi_IN", 28, "en_US"), - createDummyItem(imeY1, "Y", "E", "ja", 11, "en_US"), - createDummyItem(imeY1, "Y", "Z", "ja", 14, "en_US"), - createDummyItem(imeY1, "Y", "", "ja", 17, "en_US"), + createTestItem(imeY1, "Y", "A", "hi_IN", 28), + createTestItem(imeY1, "Y", "E", "ja", 11), + createTestItem(imeY1, "Y", "Z", "ja", 14), + createTestItem(imeY1, "Y", "", "ja", 17), // Subtypes of IME Z. // Subtypes that has the same locale of the system's. - createDummyItem(imeZ1, "", "E", "en_US", 18, "en_US"), - createDummyItem(imeZ1, "", "Z", "en_US", 21, "en_US"), - createDummyItem(imeZ1, "", "", "en_US", 24, "en_US"), + createTestItem(imeZ1, "", "E", "en_US", 18), + createTestItem(imeZ1, "", "Z", "en_US", 21), + createTestItem(imeZ1, "", "", "en_US", 24), // Subtypes that has the same language of the system's. - createDummyItem(imeZ1, "", "E", "en", 19, "en_US"), - createDummyItem(imeZ1, "", "Z", "en", 22, "en_US"), - createDummyItem(imeZ1, "", "", "en", 25, "en_US"), + createTestItem(imeZ1, "", "E", "en", 19), + createTestItem(imeZ1, "", "Z", "en", 22), + createTestItem(imeZ1, "", "", "en", 25), // Subtypes that has different language than the system's. - createDummyItem(imeZ1, "", "A", "hi_IN", 29, "en_US"), - createDummyItem(imeZ1, "", "E", "ja", 20, "en_US"), - createDummyItem(imeZ1, "", "Z", "ja", 23, "en_US"), - createDummyItem(imeZ1, "", "", "ja", 26, "en_US")); + createTestItem(imeZ1, "", "A", "hi_IN", 29), + createTestItem(imeZ1, "", "E", "ja", 20), + createTestItem(imeZ1, "", "Z", "ja", 23), + createTestItem(imeZ1, "", "", "ja", 26)); // Ensure {@link java.lang.Comparable#compareTo} contracts are satisfied. for (int i = 0; i < items.size(); ++i) { final ImeSubtypeListItem item1 = items.get(i); // Ensures sgn(x.compareTo(y)) == -sgn(y.compareTo(x)). - assertTrue(item1 + " has the same order of itself", item1.compareTo(item1) == 0); + assertEquals(item1 + " has the same order of itself", 0, item1.compareTo(item1)); // Ensures (x.compareTo(y) > 0 && y.compareTo(z) > 0) implies x.compareTo(z) > 0. for (int j = i + 1; j < items.size(); ++j) { final ImeSubtypeListItem item2 = items.get(j); @@ -442,26 +451,24 @@ public final class InputMethodSubtypeSwitchingControllerTest { { // Following two items have the same priority. - final ImeSubtypeListItem nonSystemLocale1 = - createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US"); - final ImeSubtypeListItem nonSystemLocale2 = - createDummyItem(imeX1, "X", "A", "hi_IN", 1, "en_US"); - assertTrue(nonSystemLocale1.compareTo(nonSystemLocale2) == 0); - assertTrue(nonSystemLocale2.compareTo(nonSystemLocale1) == 0); + final ImeSubtypeListItem nonSystemLocale1 = createTestItem(imeX1, "X", "A", "ja_JP", 0); + final ImeSubtypeListItem nonSystemLocale2 = createTestItem(imeX1, "X", "A", "hi_IN", 1); + assertEquals(0, nonSystemLocale1.compareTo(nonSystemLocale2)); + assertEquals(0, nonSystemLocale2.compareTo(nonSystemLocale1)); // But those aren't equal to each other. - assertFalse(nonSystemLocale1.equals(nonSystemLocale2)); - assertFalse(nonSystemLocale2.equals(nonSystemLocale1)); + assertNotEquals(nonSystemLocale1, nonSystemLocale2); + assertNotEquals(nonSystemLocale2, nonSystemLocale1); } { // Check if ComponentName is also taken into account when comparing two items. - final ImeSubtypeListItem ime1 = createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US"); - final ImeSubtypeListItem ime2 = createDummyItem(imeX2, "X", "A", "ja_JP", 0, "en_US"); + final ImeSubtypeListItem ime1 = createTestItem(imeX1, "X", "A", "ja_JP", 0); + final ImeSubtypeListItem ime2 = createTestItem(imeX2, "X", "A", "ja_JP", 0); assertTrue(ime1.compareTo(ime2) < 0); assertTrue(ime2.compareTo(ime1) > 0); // But those aren't equal to each other. - assertFalse(ime1.equals(ime2)); - assertFalse(ime2.equals(ime1)); + assertNotEquals(ime1, ime2); + assertNotEquals(ime2, ime1); } } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java index c3a87dafe7ce..79943f6330ba 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java @@ -30,6 +30,7 @@ import android.platform.test.ravenwood.RavenwoodRule; import com.android.server.pm.UserManagerInternal; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -63,6 +64,7 @@ public final class UserDataRepositoryTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + SecureSettingsWrapper.startTestMode(); mHandler = new Handler(Looper.getMainLooper()); mBindingControllerFactory = new IntFunction<InputMethodBindingController>() { @@ -73,6 +75,11 @@ public final class UserDataRepositoryTest { }; } + @After + public void tearDown() { + SecureSettingsWrapper.endTestMode(); + } + @Test public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() { // Create UserDataRepository and capture the user lifecycle listener diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java index 440905137cf9..30c384a03883 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java @@ -94,4 +94,11 @@ public class DisplayOffloadSessionImplTest { verify(mDisplayOffloader).onBlockingScreenOn(eq(unblocker)); } + + @Test + public void testUnblockScreenOn() { + mSession.cancelBlockScreenOn(); + + verify(mDisplayOffloader).cancelBlockScreenOn(); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 95f0b65ec35c..bb774eec9d4e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -105,7 +105,6 @@ import org.mockito.stubbing.Answer; import java.util.List; - @SmallTest @RunWith(AndroidJUnit4.class) public final class DisplayPowerControllerTest { @@ -1660,6 +1659,8 @@ public final class DisplayPowerControllerTest { int initState = Display.STATE_OFF; mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true); + // start with OFF. when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -1673,6 +1674,7 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState verify(mDisplayOffloadSession).blockScreenOn(any(Runnable.class)); + verify(mDisplayOffloadSession, never()).cancelBlockScreenOn(); } @Test @@ -1680,6 +1682,8 @@ public final class DisplayPowerControllerTest { // set up. int initState = Display.STATE_ON; mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true); + // start with ON. when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -1692,7 +1696,78 @@ public final class DisplayPowerControllerTest { mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState - verify(mDisplayOffloadSession, never()).blockScreenOn(any(Runnable.class)); + // No cancelBlockScreenOn call because we didn't block. + verify(mDisplayOffloadSession, never()).cancelBlockScreenOn(); + } + + @RequiresFlagsEnabled(Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON) + @Test + public void testOffloadBlocker_turnON_thenOFF_cancelBlockScreenOnNotCalledIfUnblocked() { + // Set up. + int initState = Display.STATE_OFF; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true); + + // Start with OFF. + when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_OFF; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // Go to ON. + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mDisplayOffloadSession).blockScreenOn(argumentCaptor.capture()); + + // Unblocked + argumentCaptor.getValue().run(); + advanceTime(1); // Run updatePowerState + + // Go to OFF immediately + dpr.policy = DisplayPowerRequest.POLICY_OFF; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // No cancelBlockScreenOn call because we already unblocked + verify(mDisplayOffloadSession, never()).cancelBlockScreenOn(); + } + + @RequiresFlagsEnabled(Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON) + @Test + public void testOffloadBlocker_turnON_thenOFF_cancelBlockScreenOn() { + // Set up. + int initState = Display.STATE_OFF; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true); + + // Start with OFF. + when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_OFF; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // Go to ON. + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // We should call blockScreenOn + verify(mDisplayOffloadSession).blockScreenOn(any(Runnable.class)); + + // Go to OFF immediately + dpr.policy = DisplayPowerRequest.POLICY_OFF; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // We should call cancelBlockScreenOn + verify(mDisplayOffloadSession).cancelBlockScreenOn(); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 01ff35fc088c..a7e0ebdd6de1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -33,7 +33,6 @@ import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; @@ -83,7 +82,6 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - @SmallTest @RunWith(AndroidJUnit4.class) public class LocalDisplayAdapterTest { @@ -126,7 +124,7 @@ public class LocalDisplayAdapterTest { private DisplayOffloadSessionImpl mDisplayOffloadSession; - private DisplayOffloader mDisplayOffloader; + @Mock DisplayOffloader mDisplayOffloader; private TestListener mListener = new TestListener(); @@ -1249,24 +1247,8 @@ public class LocalDisplayAdapterTest { } private void initDisplayOffloadSession() { - mDisplayOffloader = spy(new DisplayOffloader() { - @Override - public boolean startOffload() { - return true; - } - - @Override - public void stopOffload() {} - - @Override - public void onBlockingScreenOn(Runnable unblocker) {} - - @Override - public boolean allowAutoBrightnessInDoze() { - return true; - } - }); - + when(mDisplayOffloader.startOffload()).thenReturn(true); + when(mDisplayOffloader.allowAutoBrightnessInDoze()).thenReturn(true); mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader, mMockedDisplayPowerController); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index 69043f5704de..e982153acbd1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -16,19 +16,19 @@ package com.android.server.display.brightness.clamper; +import static android.view.Display.STATE_OFF; import static android.view.Display.STATE_ON; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal; @@ -40,12 +40,10 @@ import android.testing.TestableContext; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig; -import com.android.server.display.TestUtils; import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.config.SensorData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.testutils.OffsettableClock; @@ -63,6 +61,7 @@ import java.util.List; @SmallTest public class BrightnessClamperControllerTest { private static final float FLOAT_TOLERANCE = 0.001f; + private static final int DISPLAY_ID = 2; private final OffsettableClock mClock = new OffsettableClock(); private final TestHandler mTestHandler = new TestHandler(null, mClock); @@ -78,8 +77,12 @@ public class BrightnessClamperControllerTest { @Mock private BrightnessClamperController.DisplayDeviceData mMockDisplayDeviceData; @Mock + private SensorData mMockSensorData; + @Mock private DeviceConfigParameterProvider mMockDeviceConfigParameterProvider; @Mock + private LightSensorController mMockLightSensorController; + @Mock private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper; @Mock private DisplayManagerFlags mFlags; @@ -88,23 +91,17 @@ public class BrightnessClamperControllerTest { @Mock private DisplayManagerInternal.DisplayPowerRequest mMockRequest; - Sensor mLightSensor; - @Mock private DeviceConfig.Properties mMockProperties; private BrightnessClamperController mClamperController; private TestInjector mTestInjector; - @Rule - public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); - @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); mTestInjector = new TestInjector(List.of(mMockClamper), List.of(mMockModifier)); - when(mSensorManager.getDefaultSensor(anyInt())).thenReturn(mLightSensor); - when(mMockModifier.shouldListenToLightSensor()).thenReturn(true); + when(mMockDisplayDeviceData.getDisplayId()).thenReturn(DISPLAY_ID); + when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData); mClamperController = createBrightnessClamperController(); } @@ -115,6 +112,25 @@ public class BrightnessClamperControllerTest { } @Test + public void testConstructor_ConfiguresLightSensorController() { + verify(mMockLightSensorController).configure(mMockSensorData, DISPLAY_ID); + } + + @Test + public void testConstructor_doesNotStartsLightSensorController() { + verify(mMockLightSensorController, never()).restart(); + } + + @Test + public void testConstructor_startsLightSensorController() { + when(mMockModifier.shouldListenToLightSensor()).thenReturn(true); + + mClamperController = createBrightnessClamperController(); + + verify(mMockLightSensorController).restart(); + } + + @Test public void testStop_RemovesOnPropertiesChangeListener() { ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> captor = ArgumentCaptor.forClass( DeviceConfig.OnPropertiesChangedListener.class); @@ -152,6 +168,21 @@ public class BrightnessClamperControllerTest { } @Test + public void testOnDisplayChanged_doesNotRestartLightSensor() { + mClamperController.onDisplayChanged(mMockDisplayDeviceData); + + verify(mMockLightSensorController, never()).restart(); + } + + @Test + public void testOnDisplayChanged_restartsLightSensor() { + when(mMockModifier.shouldListenToLightSensor()).thenReturn(true); + mClamperController.onDisplayChanged(mMockDisplayDeviceData); + + verify(mMockLightSensorController).restart(); + } + + @Test public void testClamp_AppliesModifier() { float initialBrightness = 0.2f; boolean initialSlowChange = true; @@ -161,6 +192,26 @@ public class BrightnessClamperControllerTest { } @Test + public void testClamp_restartsLightSensor() { + float initialBrightness = 0.2f; + boolean initialSlowChange = true; + when(mMockModifier.shouldListenToLightSensor()).thenReturn(true); + mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON); + + verify(mMockLightSensorController).restart(); + } + + @Test + public void testClamp_stopsLightSensor() { + float initialBrightness = 0.2f; + boolean initialSlowChange = true; + clearInvocations(mMockLightSensorController); + mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_OFF); + + verify(mMockLightSensorController).stop(); + } + + @Test public void testClamp_inactiveClamperNotApplied() { float initialBrightness = 0.8f; boolean initialSlowChange = true; @@ -260,33 +311,17 @@ public class BrightnessClamperControllerTest { } @Test - public void testAmbientLuxChanges() throws Exception { - ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass( - SensorEventListener.class); - - verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), - anyInt(), any(Handler.class)); - SensorEventListener listener = listenerCaptor.getValue(); - - when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL))).thenReturn(List.of(mLightSensor)); - - float initialBrightness = 0.8f; - boolean initialSlowChange = true; - - DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness, - initialSlowChange, STATE_ON); - assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE); + public void testAmbientLuxChanges() { + mTestInjector.mCapturedLightSensorListener.onAmbientLuxChange(50); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 50, mClock.now())); verify(mMockModifier).setAmbientLux(50); - - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 300, mClock.now())); - verify(mMockModifier).setAmbientLux(300); } @Test public void testStop() { + clearInvocations(mMockLightSensorController); mClamperController.stop(); + verify(mMockLightSensorController).stop(); verify(mMockModifier).stop(); verify(mMockClamper).stop(); } @@ -303,6 +338,7 @@ public class BrightnessClamperControllerTest { private final List<BrightnessStateModifier> mModifiers; private BrightnessClamperController.ClamperChangeListener mCapturedChangeListener; + private LightSensorController.LightSensorListener mCapturedLightSensorListener; private TestInjector( List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> @@ -330,8 +366,15 @@ public class BrightnessClamperControllerTest { @Override List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context, Handler handler, BrightnessClamperController.ClamperChangeListener listener, - DisplayDeviceConfig displayDeviceConfig, SensorManager sensorManager) { + DisplayDeviceConfig displayDeviceConfig) { return mModifiers; } + + @Override + LightSensorController getLightSensorController(SensorManager sensorManager, Context context, + LightSensorController.LightSensorListener listener, Handler handler) { + mCapturedLightSensorListener = listener; + return mMockLightSensorController; + } } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt new file mode 100644 index 000000000000..b742d021f2e9 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.brightness.clamper + +import android.content.res.Resources +import android.hardware.Sensor +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.os.Handler +import androidx.test.filters.SmallTest +import com.android.server.display.TestUtils +import com.android.server.display.brightness.clamper.LightSensorController.Injector +import com.android.server.display.brightness.clamper.LightSensorController.LightSensorListener +import com.android.server.display.config.SensorData +import com.android.server.display.utils.AmbientFilter +import org.junit.Before +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.inOrder +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever + +private const val LIGHT_SENSOR_RATE: Int = 10 +private const val DISPLAY_ID: Int = 3 +private const val NOW: Long = 3_000 + +@SmallTest +class LightSensorControllerTest { + + private val mockSensorManager: SensorManager = mock() + private val mockResources: Resources = mock() + private val mockLightSensorListener: LightSensorListener = mock() + private val mockHandler: Handler = mock() + private val mockAmbientFilter: AmbientFilter = mock() + + private val testInjector = TestInjector() + private val dummySensorData = SensorData() + + private lateinit var controller: LightSensorController + + @Before + fun setUp() { + controller = LightSensorController(mockSensorManager, mockResources, + mockLightSensorListener, mockHandler, testInjector) + } + + fun `does not register light sensor if is not configured`() { + controller.restart() + + verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener) + } + + fun `does not register light sensor if missing`() { + controller.configure(dummySensorData, DISPLAY_ID) + controller.restart() + + verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener) + } + + fun `register light sensor if configured and present`() { + testInjector.lightSensor = TestUtils + .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT) + controller.configure(dummySensorData, DISPLAY_ID) + controller.restart() + + verify(mockSensorManager).registerListener(any(), + testInjector.lightSensor, LIGHT_SENSOR_RATE * 1000, mockHandler) + verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener) + } + + fun `register light sensor once if not changed`() { + testInjector.lightSensor = TestUtils + .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT) + controller.configure(dummySensorData, DISPLAY_ID) + + controller.restart() + controller.restart() + + verify(mockSensorManager).registerListener(any(), + testInjector.lightSensor, LIGHT_SENSOR_RATE * 1000, mockHandler) + verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener) + } + + fun `register new light sensor and unregister old if changed`() { + val lightSensor1 = TestUtils + .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT) + testInjector.lightSensor = lightSensor1 + controller.configure(dummySensorData, DISPLAY_ID) + controller.restart() + + val lightSensor2 = TestUtils + .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT) + testInjector.lightSensor = lightSensor2 + controller.configure(dummySensorData, DISPLAY_ID) + controller.restart() + + inOrder { + verify(mockSensorManager).registerListener(any(), + lightSensor1, LIGHT_SENSOR_RATE * 1000, mockHandler) + verify(mockSensorManager).unregisterListener(any<SensorEventListener>()) + verify(mockAmbientFilter).clear() + verify(mockLightSensorListener).onAmbientLuxChange(LightSensorController.INVALID_LUX) + verify(mockSensorManager).registerListener(any(), + lightSensor2, LIGHT_SENSOR_RATE * 1000, mockHandler) + } + verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener) + } + + fun `notifies listener on ambient lux change`() { + val expectedLux = 40f + val eventLux = 50 + val eventTime = 60L + whenever(mockAmbientFilter.getEstimate(NOW)).thenReturn(expectedLux) + val listenerCaptor = argumentCaptor<SensorEventListener>() + testInjector.lightSensor = TestUtils + .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT) + controller.configure(dummySensorData, DISPLAY_ID) + controller.restart() + verify(mockSensorManager).registerListener(listenerCaptor.capture(), + eq(testInjector.lightSensor), eq(LIGHT_SENSOR_RATE * 1000), eq(mockHandler)) + + val listener = listenerCaptor.lastValue + listener.onSensorChanged(TestUtils.createSensorEvent(testInjector.lightSensor, + eventLux, eventTime * 1_000_000)) + + inOrder { + verify(mockAmbientFilter).addValue(eventTime, eventLux.toFloat()) + verify(mockLightSensorListener).onAmbientLuxChange(expectedLux) + } + } + + private inner class TestInjector : Injector() { + var lightSensor: Sensor? = null + override fun getLightSensor(sensorManager: SensorManager?, + sensorData: SensorData?, fallbackType: Int): Sensor? { + return lightSensor + } + + override fun getLightSensorRate(resources: Resources?): Int { + return LIGHT_SENSOR_RATE + } + + override fun getAmbientFilter(resources: Resources?): AmbientFilter { + return mockAmbientFilter + } + + override fun getTime(): Long { + return NOW + } + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java index 498bffd260dd..4e10b98c0041 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java @@ -192,7 +192,7 @@ public class AutomaticBrightnessStrategy2Test { } @Test - public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() { + public void testAutoBrightnessState_DeviceIsInDoze_ConfigDoesAllow() { mAutomaticBrightnessStrategy.setUseAutoBrightness(true); int targetDisplayState = Display.STATE_DOZE; boolean allowAutoBrightnessWhileDozing = true; @@ -218,6 +218,32 @@ public class AutomaticBrightnessStrategy2Test { } @Test + public void testAutoBrightnessState_DeviceIsInDoze_ConfigDoesAllow_ScreenOff() { + mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + int targetDisplayState = Display.STATE_OFF; + boolean allowAutoBrightnessWhileDozing = true; + int brightnessReason = BrightnessReason.REASON_UNKNOWN; + int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + float lastUserSetBrightness = 0.2f; + boolean userSetBrightnessChanged = true; + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f); + mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(); + mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController) + .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE, + mBrightnessConfiguration, + lastUserSetBrightness, + userSetBrightnessChanged, /* adjustment */ 0.4f, + /* userChangedAutoBrightnessAdjustment= */ true, policy, + targetDisplayState, /* shouldResetShortTermModel */ true); + assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()); + assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()); + } + + @Test public void testAutoBrightnessState_DisplayIsOn() { mAutomaticBrightnessStrategy.setUseAutoBrightness(true); int targetDisplayState = Display.STATE_ON; @@ -245,6 +271,33 @@ public class AutomaticBrightnessStrategy2Test { } @Test + public void testAutoBrightnessState_DisplayIsOn_PolicyIsDoze() { + mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + int targetDisplayState = Display.STATE_ON; + boolean allowAutoBrightnessWhileDozing = false; + int brightnessReason = BrightnessReason.REASON_UNKNOWN; + float lastUserSetBrightness = 0.2f; + boolean userSetBrightnessChanged = true; + int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + float pendingBrightnessAdjustment = 0.1f; + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment); + mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(); + mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController) + .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE, + mBrightnessConfiguration, + lastUserSetBrightness, + userSetBrightnessChanged, pendingBrightnessAdjustment, + /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState, + /* shouldResetShortTermModel */ true); + assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()); + assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()); + } + + @Test public void accommodateUserBrightnessChangesWorksAsExpected() { // Verify the state if automaticBrightnessController is configured. assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive()); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index 1d04baaa6acd..e16377e57321 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -202,7 +202,7 @@ public class AutomaticBrightnessStrategyTest { } @Test - public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() { + public void testAutoBrightnessState_DeviceIsInDoze_ConfigDoesAllow() { mAutomaticBrightnessStrategy.setUseAutoBrightness(true); int targetDisplayState = Display.STATE_DOZE; boolean allowAutoBrightnessWhileDozing = true; @@ -228,6 +228,32 @@ public class AutomaticBrightnessStrategyTest { } @Test + public void testAutoBrightnessState_DeviceIsInDoze_ConfigDoesAllow_ScreenOff() { + mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + int targetDisplayState = Display.STATE_OFF; + boolean allowAutoBrightnessWhileDozing = true; + int brightnessReason = BrightnessReason.REASON_UNKNOWN; + int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + float lastUserSetBrightness = 0.2f; + boolean userSetBrightnessChanged = true; + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f); + mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(); + mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController) + .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE, + mBrightnessConfiguration, + lastUserSetBrightness, + userSetBrightnessChanged, /* adjustment */ 0.4f, + /* userChangedAutoBrightnessAdjustment= */ true, policy, + targetDisplayState, /* shouldResetShortTermModel */ true); + assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()); + assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()); + } + + @Test public void testAutoBrightnessState_DisplayIsOn() { mAutomaticBrightnessStrategy.setUseAutoBrightness(true); int targetDisplayState = Display.STATE_ON; @@ -255,6 +281,33 @@ public class AutomaticBrightnessStrategyTest { } @Test + public void testAutoBrightnessState_DisplayIsOn_PolicyIsDoze() { + mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + int targetDisplayState = Display.STATE_ON; + boolean allowAutoBrightnessWhileDozing = false; + int brightnessReason = BrightnessReason.REASON_UNKNOWN; + float lastUserSetBrightness = 0.2f; + boolean userSetBrightnessChanged = true; + int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + float pendingBrightnessAdjustment = 0.1f; + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment); + mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(); + mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController) + .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE, + mBrightnessConfiguration, + lastUserSetBrightness, + userSetBrightnessChanged, pendingBrightnessAdjustment, + /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState, + /* shouldResetShortTermModel */ true); + assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()); + assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()); + } + + @Test public void testAutoBrightnessState_modeSwitch() { // Setup the test when(mDisplayManagerFlags.areAutoBrightnessModesEnabled()).thenReturn(true); diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java index 3d03bf218557..e2b93ae3e9e7 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java @@ -205,7 +205,7 @@ public class TestDreamEnvironment { @Override public DreamOverlayConnectionHandler createOverlayConnection( - ComponentName overlayComponent) { + ComponentName overlayComponent, Runnable onDisconnected) { return mDreamOverlayConnectionHandler; } diff --git a/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java b/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java index 22d7e7300bba..3e6558532b94 100644 --- a/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java +++ b/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java @@ -49,10 +49,6 @@ import java.util.function.Consumer; @SmallTest @RunWith(AndroidJUnit4.class) public class DreamOverlayConnectionHandlerTest { - private static final int MIN_CONNECTION_DURATION_MS = 100; - private static final int MAX_RECONNECT_ATTEMPTS = 3; - private static final int BASE_RECONNECT_DELAY_MS = 50; - @Mock private Context mContext; @Mock @@ -63,6 +59,8 @@ public class DreamOverlayConnectionHandlerTest { private IDreamOverlay mOverlayService; @Mock private IDreamOverlayClient mOverlayClient; + @Mock + private Runnable mOnDisconnectRunnable; private TestLooper mTestLooper; private DreamOverlayConnectionHandler mDreamOverlayConnectionHandler; @@ -75,9 +73,7 @@ public class DreamOverlayConnectionHandlerTest { mContext, mTestLooper.getLooper(), mServiceIntent, - MIN_CONNECTION_DURATION_MS, - MAX_RECONNECT_ATTEMPTS, - BASE_RECONNECT_DELAY_MS, + mOnDisconnectRunnable, new TestInjector(mConnection)); } @@ -119,12 +115,14 @@ public class DreamOverlayConnectionHandlerTest { mTestLooper.dispatchAll(); // No client yet, so we shouldn't have executed verify(consumer, never()).accept(mOverlayClient); + verify(mOnDisconnectRunnable, never()).run(); provideClient(); // Service disconnected before looper could handle the message. disconnectService(); mTestLooper.dispatchAll(); verify(consumer, never()).accept(mOverlayClient); + verify(mOnDisconnectRunnable).run(); } @Test @@ -237,8 +235,7 @@ public class DreamOverlayConnectionHandlerTest { @Override public PersistentServiceConnection<IDreamOverlay> buildConnection(Context context, - Handler handler, Intent serviceIntent, int minConnectionDurationMs, - int maxReconnectAttempts, int baseReconnectDelayMs) { + Handler handler, Intent serviceIntent) { return mConnection; } } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java index 75e8e68dd98a..72883e269a65 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java @@ -134,6 +134,18 @@ public class UserWakeupStoreTest { } @Test + public void testOnUserStarting_userIsRemovedFromTheStore() { + mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000); + mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 7_000); + mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000); + assertEquals(3, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length); + mUserWakeupStore.onUserStarting(USER_ID_3); + // getWakeupTimeForUser returns negative wakeup time if there is no entry for user. + assertEquals(-1, mUserWakeupStore.getWakeupTimeForUser(USER_ID_3)); + assertEquals(2, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length); + } + + @Test public void testGetNextUserWakeup() { mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000); mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 3_000); diff --git a/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java index 62075b828a62..ab1b0cc51d39 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java @@ -17,11 +17,10 @@ package com.android.server.power; import static com.android.server.power.ShutdownThread.DEFAULT_SHUTDOWN_VIBRATE_MS; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,7 +41,6 @@ import org.mockito.MockitoAnnotations; import java.io.File; import java.io.FileOutputStream; -import java.io.IOException; /** * Tests for {@link com.android.server.power.ShutdownThread} @@ -88,6 +86,7 @@ public class ShutdownThreadTest { @Mock private VibratorInfo mVibratorInfoMock; private String mDefaultShutdownVibrationFilePath; + private boolean mShutdownVibrationDisabled; private long mLastSleepDurationMs; private ShutdownThread mShutdownThread; @@ -168,6 +167,17 @@ public class ShutdownThreadTest { .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class)); } + @Test + public void testVibrationDisabled() throws Exception { + setShutdownVibrationFileContent(CLICK_VIB_SERIALIZATION); + mShutdownVibrationDisabled = true; + + mShutdownThread.playShutdownVibration(mContextMock); + + verify(mVibratorMock, never()) + .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class)); + } + private void assertShutdownVibration(VibrationEffect effect, long vibrationSleepDuration) throws Exception { verify(mVibratorMock).vibrate( @@ -214,5 +224,10 @@ public class ShutdownThreadTest { public void sleep(long durationMs) { mLastSleepDurationMs = durationMs; } + + @Override + public boolean isShutdownVibrationDisabled(Context context) { + return mShutdownVibrationDisabled; + } } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java index 9975190424b5..a4688ccc04ce 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java @@ -42,6 +42,7 @@ import java.text.ParseException; @SmallTest public class AggregatedPowerStatsTest { private static final int TEST_POWER_COMPONENT = 1077; + private static final int CUSTOM_POWER_COMPONENT = 1042; private static final int APP_1 = 27; private static final int APP_2 = 42; private static final int COMPONENT_STATE_0 = 0; @@ -63,6 +64,20 @@ public class AggregatedPowerStatsTest { AggregatedPowerStatsConfig.STATE_SCREEN, AggregatedPowerStatsConfig.STATE_PROCESS_STATE); + mAggregatedPowerStatsConfig.trackCustomPowerComponents( + () -> new PowerStatsProcessor() { + @Override + void finish(PowerComponentAggregatedPowerStats stats, + long timestampMs) { + } + }) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE); SparseArray<String> stateLabels = new SparseArray<>(); stateLabels.put(COMPONENT_STATE_1, "one"); mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, @@ -137,6 +152,13 @@ public class AggregatedPowerStatsTest { ps.uidStats.put(APP_2, new long[]{100, 200, 300}); stats.addPowerStats(ps, 5000); + + PowerStats custom = new PowerStats( + new PowerStats.Descriptor(CUSTOM_POWER_COMPONENT, "cu570m", 1, null, 0, 2, + new PersistableBundle())); + custom.stats = new long[]{123}; + custom.uidStats.put(APP_1, new long[]{500, 600}); + stats.addPowerStats(custom, 6000); return stats; } @@ -149,12 +171,12 @@ public class AggregatedPowerStatsTest { assertThat(descriptor.uidStatsArrayLength).isEqualTo(3); assertThat(descriptor.extras.getString("speed")).isEqualTo("fast"); - assertThat(getDeviceStats(stats, + assertThat(getDeviceStats(stats, TEST_POWER_COMPONENT, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, AggregatedPowerStatsConfig.SCREEN_STATE_ON)) .isEqualTo(new long[]{322, 987}); - assertThat(getDeviceStats(stats, + assertThat(getDeviceStats(stats, TEST_POWER_COMPONENT, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, AggregatedPowerStatsConfig.SCREEN_STATE_OTHER)) .isEqualTo(new long[]{222, 0}); @@ -185,51 +207,79 @@ public class AggregatedPowerStatsTest { .isEqualTo(new long[]{4500}); assertThat(getUidDeviceStats(stats, - APP_1, + TEST_POWER_COMPONENT, APP_1, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, AggregatedPowerStatsConfig.SCREEN_STATE_ON, BatteryConsumer.PROCESS_STATE_UNSPECIFIED)) .isEqualTo(new long[]{259, 0, 492}); assertThat(getUidDeviceStats(stats, - APP_1, + TEST_POWER_COMPONENT, APP_1, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, AggregatedPowerStatsConfig.SCREEN_STATE_ON, BatteryConsumer.PROCESS_STATE_CACHED)) .isEqualTo(new long[]{129, 0, 446}); assertThat(getUidDeviceStats(stats, - APP_1, + TEST_POWER_COMPONENT, APP_1, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, BatteryConsumer.PROCESS_STATE_CACHED)) .isEqualTo(new long[]{0, 0, 200}); assertThat(getUidDeviceStats(stats, - APP_2, + TEST_POWER_COMPONENT, APP_2, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, AggregatedPowerStatsConfig.SCREEN_STATE_ON, BatteryConsumer.PROCESS_STATE_UNSPECIFIED)) .isEqualTo(new long[]{185, 209, 418}); assertThat(getUidDeviceStats(stats, - APP_2, + TEST_POWER_COMPONENT, APP_2, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, AggregatedPowerStatsConfig.SCREEN_STATE_ON, BatteryConsumer.PROCESS_STATE_FOREGROUND)) .isEqualTo(new long[]{142, 204, 359}); assertThat(getUidDeviceStats(stats, - APP_2, + TEST_POWER_COMPONENT, APP_2, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, BatteryConsumer.PROCESS_STATE_BACKGROUND)) .isEqualTo(new long[]{50, 100, 150}); + + descriptor = stats.getPowerComponentStats(CUSTOM_POWER_COMPONENT) + .getPowerStatsDescriptor(); + assertThat(descriptor.powerComponentId).isEqualTo(CUSTOM_POWER_COMPONENT); + assertThat(descriptor.statsArrayLength).isEqualTo(1); + assertThat(descriptor.uidStatsArrayLength).isEqualTo(2); + + assertThat(getDeviceStats(stats, CUSTOM_POWER_COMPONENT, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON)) + .isEqualTo(new long[]{61}); + assertThat(getDeviceStats(stats, CUSTOM_POWER_COMPONENT, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER)) + .isEqualTo(new long[]{61}); + assertThat(getUidDeviceStats(stats, + CUSTOM_POWER_COMPONENT, APP_1, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, + BatteryConsumer.PROCESS_STATE_CACHED)) + .isEqualTo(new long[]{250, 300}); + assertThat(getUidDeviceStats(stats, + CUSTOM_POWER_COMPONENT, APP_1, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, + BatteryConsumer.PROCESS_STATE_CACHED)) + .isEqualTo(new long[]{250, 300}); } - private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) { + private static long[] getDeviceStats(AggregatedPowerStats stats, int powerComponentId, + int... states) { PowerComponentAggregatedPowerStats powerComponentStats = - stats.getPowerComponentStats(TEST_POWER_COMPONENT); + stats.getPowerComponentStats(powerComponentId); long[] out = new long[powerComponentStats.getPowerStatsDescriptor().statsArrayLength]; powerComponentStats.getDeviceStats(out, states); return out; @@ -243,9 +293,10 @@ public class AggregatedPowerStatsTest { return out; } - private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) { + private static long[] getUidDeviceStats(AggregatedPowerStats stats, int powerComponentId, + int uid, int... states) { PowerComponentAggregatedPowerStats powerComponentStats = - stats.getPowerComponentStats(TEST_POWER_COMPONENT); + stats.getPowerComponentStats(powerComponentId); long[] out = new long[powerComponentStats.getPowerStatsDescriptor().uidStatsArrayLength]; powerComponentStats.getUidStats(out, uid, states); return out; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java index 36deb08de8de..efbd1b7f42fd 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java @@ -30,8 +30,11 @@ import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SC import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; import android.os.BatteryStats; @@ -116,8 +119,10 @@ public class CameraPowerStatsTest { @Test public void energyConsumerModel() { - when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CAMERA, null)) + when(mConsumedEnergyRetriever + .getEnergyConsumerIds(eq((int) EnergyConsumerType.CAMERA), any())) .thenReturn(new int[]{ENERGY_CONSUMER_ID}); + CameraPowerStatsProcessor processor = new CameraPowerStatsProcessor( mStatsRule.getPowerProfile(), mUidResolver); @@ -131,8 +136,8 @@ public class CameraPowerStatsTest { collector.setEnabled(true); // Establish a baseline - when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID})) - .thenReturn(new long[]{uCtoUj(10000)}); + when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID})) + .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 10000)); collector.collectAndDeliverStats(); processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1)); @@ -144,15 +149,15 @@ public class CameraPowerStatsTest { processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1)); - when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID})) - .thenReturn(new long[]{uCtoUj(2_170_000)}); + when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID})) + .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 2_170_000)); collector.collectAndDeliverStats(); processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2)); mStatsRule.setTime(11_000, 11_000); - when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID})) - .thenReturn(new long[]{uCtoUj(3_610_000)}); + when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID})) + .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 3_610_000)); collector.collectAndDeliverStats(); processor.finish(stats, 11_000); @@ -264,7 +269,10 @@ public class CameraPowerStatsTest { return powerComponentStats; } - private static long uCtoUj(long uc) { - return (long) (uc * (double) VOLTAGE_MV / 1000); + private EnergyConsumerResult[] createEnergyConsumerResults(int id, long energyUWs) { + EnergyConsumerResult result = new EnergyConsumerResult(); + result.id = id; + result.energyUWs = (long) (energyUWs * (double) VOLTAGE_MV / 1000); + return new EnergyConsumerResult[]{result}; } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java new file mode 100644 index 000000000000..7bec13f653e7 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.power.stats; + +import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND; +import static android.os.BatteryConsumer.PROCESS_STATE_CACHED; +import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND; +import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE; + +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.hardware.power.stats.EnergyConsumerAttribution; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; +import android.os.Handler; +import android.os.Process; +import android.platform.test.ravenwood.RavenwoodRule; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.IntSupplier; + +public class CustomEnergyConsumerPowerStatsTest { + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); + + public static final int ENERGY_CONSUMER_ID1 = 77; + public static final int ENERGY_CONSUMER_ID2 = 88; + private static final int VOLTAGE_MV = 3500; + private static final double PRECISION = 0.00001; + private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42; + private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101; + private static final EnergyConsumerPowerStatsLayout POWER_STATS_LAYOUT = + new EnergyConsumerPowerStatsLayout(); + + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + + private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver(); + + private EnergyConsumerPowerStatsCollector.Injector mInjector = + new EnergyConsumerPowerStatsCollector.Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> VOLTAGE_MV; + } + }; + + + private CustomEnergyConsumerPowerStatsCollector mCollector; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mCollector = new CustomEnergyConsumerPowerStatsCollector(mInjector); + mCollector.setEnabled(true); + } + + @Test + public void collectStats() throws Exception { + // Establish a baseline + collectPowerStats(0, 10_000, 30_000, + createAttribution(APP_UID1, 10_000), + createAttribution(APP_UID2, 15_000)); + + List<PowerStats> results = collectPowerStats(12345, 45_000, 100_000, + createAttribution(APP_UID1, 24_000), + createAttribution(APP_UID2, 36_000)); + + assertThat(results).hasSize(2); + PowerStats ps1 = results.stream() + .filter(ps -> ps.descriptor.name.equals("FOO")).findAny().orElseThrow(); + assertThat(ps1.durationMs).isEqualTo(12345); + + // Energy (uWs) / (voltage (mV) / 1000) -> charge (uC) + assertThat(POWER_STATS_LAYOUT.getConsumedEnergy(ps1.stats, 0)) + .isEqualTo(10000); + assertThat(ps1.uidStats.size()).isEqualTo(0); + + PowerStats ps2 = results.stream() + .filter(ps -> ps.descriptor.name.equals("BAR")).findAny().orElseThrow(); + assertThat(POWER_STATS_LAYOUT.getConsumedEnergy(ps2.stats, 0)) + .isEqualTo(20000); + assertThat(ps2.uidStats.size()).isEqualTo(2); + assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID1), 0)) + .isEqualTo(14000); + assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID2), 0)) + .isEqualTo(21000); + } + + @Test + public void processStats() throws Exception { + AggregatedPowerStats aggregatedPowerStats = createAggregatedPowerStats(); + aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0); + aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0); + aggregatedPowerStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, + 0); + aggregatedPowerStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); + + List<PowerStats> results = collectPowerStats(0, 10_000, 30_000, + createAttribution(APP_UID1, 10_000), + createAttribution(APP_UID2, 15_000)); + for (PowerStats powerStats : results) { + aggregatedPowerStats.addPowerStats(powerStats, 0); + } + + aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_BATTERY, 10_000); + aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_OTHER, 10_000); + aggregatedPowerStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, + 10_000); + aggregatedPowerStats.setUidState(APP_UID2, STATE_PROCESS_STATE, + PROCESS_STATE_FOREGROUND_SERVICE, 10_000); + + results = collectPowerStats(12345, 45_000, 100_000, + createAttribution(APP_UID1, 24_000), + createAttribution(APP_UID2, 36_000)); + for (PowerStats powerStats : results) { + aggregatedPowerStats.addPowerStats(powerStats, 40_000); + } + + aggregatedPowerStats.finish(40_0000); + + List<PowerComponentAggregatedPowerStats> powerComponentStats = + aggregatedPowerStats.getPowerComponentStats(); + + PowerComponentAggregatedPowerStats ps1 = powerComponentStats.stream() + .filter(ps -> ps.getPowerStatsDescriptor().name.equals("FOO")).findAny() + .orElseThrow(); + PowerComponentAggregatedPowerStats ps2 = powerComponentStats.stream() + .filter(ps -> ps.getPowerStatsDescriptor().name.equals("BAR")).findAny() + .orElseThrow(); + + long[] deviceStats = new long[ps1.getPowerStatsDescriptor().statsArrayLength]; + long[] uidStats = new long[ps1.getPowerStatsDescriptor().uidStatsArrayLength]; + + // Total estimated power = 10,000 uC = 0.00278 mAh + double expectedPower = 0.00278; + ps1.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(POWER_STATS_LAYOUT.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(expectedPower * 0.25); + + ps1.getDeviceStats(deviceStats, states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER)); + assertThat(POWER_STATS_LAYOUT.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(expectedPower * 0.75); + + // UID1: estimated power = 14,000 uC = 0.00388 mAh + expectedPower = 0.00388; + ps2.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); + assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower * 0.25); + + ps2.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); + assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower * 0.75); + + // UID2: estimated power = 21,000 uC = 0.00583 mAh + expectedPower = 0.00583; + ps2.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); + assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower * 0.25); + + ps2.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); + assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower * 0.75); + } + + private List<PowerStats> collectPowerStats(long timestamp, int chargeUc1, int chargeUc2, + EnergyConsumerAttribution... attributions2) throws Exception { + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.OTHER)) + .thenReturn(new int[]{ENERGY_CONSUMER_ID1, ENERGY_CONSUMER_ID2}); + when(mConsumedEnergyRetriever.getEnergyConsumerName(ENERGY_CONSUMER_ID1)) + .thenReturn("FOO"); + when(mConsumedEnergyRetriever.getEnergyConsumerName(ENERGY_CONSUMER_ID2)) + .thenReturn("BAR"); + when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID1})) + .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID1, chargeUc1, null)); + when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID2})) + .thenReturn( + createEnergyConsumerResults(ENERGY_CONSUMER_ID2, chargeUc2, attributions2)); + + mStatsRule.setTime(timestamp, timestamp); + List<PowerStats> results = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(2); + Consumer<PowerStats> consumer = powerStats -> { + results.add(powerStats); + latch.countDown(); + }; + mCollector.addConsumer(consumer); + mCollector.schedule(); + assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); + mCollector.removeConsumer(consumer); + return results; + } + + private static AggregatedPowerStats createAggregatedPowerStats() { + AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); + config.trackCustomPowerComponents(CustomEnergyConsumerPowerStatsProcessor::new) + .trackDeviceStates( + STATE_POWER, + STATE_SCREEN) + .trackUidStates( + STATE_POWER, + STATE_SCREEN, + STATE_PROCESS_STATE); + + return new AggregatedPowerStats(config); + } + + private EnergyConsumerResult[] createEnergyConsumerResults(int id, long energyUws, + EnergyConsumerAttribution[] attributions) { + EnergyConsumerResult result = new EnergyConsumerResult(); + result.id = id; + result.energyUWs = energyUws; + result.attribution = attributions; + return new EnergyConsumerResult[]{result}; + } + + private EnergyConsumerAttribution createAttribution(int uid, long energyUWs) { + EnergyConsumerAttribution attribution = new EnergyConsumerAttribution(); + attribution.uid = uid; + attribution.energyUWs = energyUWs; + return attribution; + } + + private int[] states(int... states) { + return states; + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java index 8a391c6bb2ea..774be897692a 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java @@ -30,8 +30,11 @@ import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SC import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.location.GnssSignalQuality; import android.os.BatteryConsumer; @@ -119,8 +122,10 @@ public class GnssPowerStatsTest { @Test public void powerProfileModel() { // ODPM unsupported - when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.GNSS, null)) + when(mConsumedEnergyRetriever + .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS), any())) .thenReturn(new int[0]); + GnssPowerStatsProcessor processor = new GnssPowerStatsProcessor( mStatsRule.getPowerProfile(), mUidResolver); @@ -209,7 +214,8 @@ public class GnssPowerStatsTest { @Test public void energyConsumerModel() { - when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.GNSS, null)) + when(mConsumedEnergyRetriever + .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS), any())) .thenReturn(new int[]{ENERGY_CONSUMER_ID}); GnssPowerStatsProcessor processor = new GnssPowerStatsProcessor( mStatsRule.getPowerProfile(), mUidResolver); @@ -224,8 +230,8 @@ public class GnssPowerStatsTest { collector.setEnabled(true); // Establish a baseline - when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID})) - .thenReturn(new long[]{uCtoUj(10000)}); + when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID})) + .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 10000)); collector.collectAndDeliverStats(); processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1)); @@ -237,8 +243,8 @@ public class GnssPowerStatsTest { processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1)); - when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID})) - .thenReturn(new long[]{uCtoUj(2_170_000)}); + when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID})) + .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 2_170_000)); collector.collectAndDeliverStats(); processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2)); @@ -247,8 +253,8 @@ public class GnssPowerStatsTest { processor.noteStateChange(stats, buildHistoryItem(8000, GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR)); mStatsRule.setTime(11_000, 11_000); - when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID})) - .thenReturn(new long[]{uCtoUj(3_610_000)}); + when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID})) + .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 3_610_000)); collector.collectAndDeliverStats(); processor.finish(stats, 11_000); @@ -369,7 +375,10 @@ public class GnssPowerStatsTest { return powerComponentStats; } - private static long uCtoUj(long uc) { - return (long) (uc * (double) VOLTAGE_MV / 1000); + private EnergyConsumerResult[] createEnergyConsumerResults(int id, long energyUWs) { + EnergyConsumerResult result = new EnergyConsumerResult(); + result.id = id; + result.energyUWs = (long) (energyUWs * (double) VOLTAGE_MV / 1000); + return new EnergyConsumerResult[]{result}; } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java index 412fc88dcd27..32bfb2cd3507 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java @@ -79,6 +79,8 @@ public class PowerStatsExporterTest { private BatteryStatsHistory mHistory; private CpuPowerStatsLayout mCpuStatsArrayLayout; private PowerStats.Descriptor mPowerStatsDescriptor; + private final EnergyConsumerPowerStatsLayout mEnergyConsumerPowerStatsLayout = + new EnergyConsumerPowerStatsLayout(); @Before public void setup() throws IOException { @@ -87,14 +89,24 @@ public class PowerStatsExporterTest { AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) - .trackDeviceStates(AggregatedPowerStatsConfig.STATE_POWER, + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, AggregatedPowerStatsConfig.STATE_SCREEN) - .trackUidStates(AggregatedPowerStatsConfig.STATE_POWER, + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, AggregatedPowerStatsConfig.STATE_SCREEN, AggregatedPowerStatsConfig.STATE_PROCESS_STATE) .setProcessor( new CpuPowerStatsProcessor(mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies())); + config.trackCustomPowerComponents(CustomEnergyConsumerPowerStatsProcessor::new) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE); mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler(), config); mHistory = new BatteryStatsHistory(Parcel.obtain(), storeDirectory, 0, 10000, @@ -120,15 +132,25 @@ public class PowerStatsExporterTest { @Test public void breakdownByProcState_fullRange() throws Exception { BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( - new String[0], /* includePowerModels */ false, + new String[]{"cu570m"}, /* includePowerModels */ false, /* includeProcessStateData */ true, /* powerThreshold */ 0); exportAggregatedPowerStats(builder, 1000, 10000); BatteryUsageStats actual = builder.build(); String message = "Actual BatteryUsageStats: " + actual; - assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); - assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); + assertAggregatedPowerEstimate(message, actual, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, + BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); + assertAggregatedPowerEstimate(message, actual, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, + BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); + assertAggregatedPowerEstimate(message, actual, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 3.60); + assertAggregatedPowerEstimate(message, actual, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 0.360); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_ANY, 3.97099); @@ -136,11 +158,17 @@ public class PowerStatsExporterTest { BatteryConsumer.PROCESS_STATE_FOREGROUND, 2.198082); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_BACKGROUND, 1.772916); + assertUidPowerEstimate(message, actual, APP_UID1, + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, + BatteryConsumer.PROCESS_STATE_ANY, 0.360); assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_ANY, 3.538999); assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 3.538999); + assertUidPowerEstimate(message, actual, APP_UID2, + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, + BatteryConsumer.PROCESS_STATE_ANY, 0); actual.close(); } @@ -148,15 +176,19 @@ public class PowerStatsExporterTest { @Test public void breakdownByProcState_subRange() throws Exception { BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( - new String[0], /* includePowerModels */ false, + new String[]{"cu570m"}, /* includePowerModels */ false, /* includeProcessStateData */ true, /* powerThreshold */ 0); exportAggregatedPowerStats(builder, 3700, 6700); BatteryUsageStats actual = builder.build(); String message = "Actual BatteryUsageStats: " + actual; - assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 4.526749); - assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 4.526749); + assertAggregatedPowerEstimate(message, actual, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, + BatteryConsumer.POWER_COMPONENT_CPU, 4.526749); + assertAggregatedPowerEstimate(message, actual, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, + BatteryConsumer.POWER_COMPONENT_CPU, 4.526749); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_ANY, 1.193332); @@ -176,15 +208,19 @@ public class PowerStatsExporterTest { @Test public void combinedProcessStates() throws Exception { BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( - new String[0], /* includePowerModels */ false, + new String[]{"cu570m"}, /* includePowerModels */ false, /* includeProcessStateData */ false, /* powerThreshold */ 0); exportAggregatedPowerStats(builder, 1000, 10000); BatteryUsageStats actual = builder.build(); String message = "Actual BatteryUsageStats: " + actual; - assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); - assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); + assertAggregatedPowerEstimate(message, actual, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, + BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); + assertAggregatedPowerEstimate(message, actual, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, + BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_ANY, 3.97099); @@ -210,10 +246,19 @@ public class PowerStatsExporterTest { long[] uidStats2 = new long[mCpuStatsArrayLayout.getUidStatsArrayLength()]; powerStats.uidStats.put(APP_UID2, uidStats2); + PowerStats customPowerStats = new PowerStats( + new PowerStats.Descriptor(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, + "cu570m", mEnergyConsumerPowerStatsLayout.getDeviceStatsArrayLength(), + null, 0, mEnergyConsumerPowerStatsLayout.getUidStatsArrayLength(), + new PersistableBundle())); + long[] customUidStats = new long[mEnergyConsumerPowerStatsLayout.getUidStatsArrayLength()]; + customPowerStats.uidStats.put(APP_UID1, customUidStats); + mHistory.forceRecordAllHistory(); mHistory.startRecordingHistory(1000, 1000, false); mHistory.recordPowerStats(1000, 1000, powerStats); + mHistory.recordPowerStats(1000, 1000, customPowerStats); mHistory.recordBatteryState(1000, 1000, 70, /* plugged */ false); mHistory.recordStateStartEvent(1000, 1000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); mHistory.recordProcessStateChange(1000, 1000, APP_UID1, @@ -245,6 +290,10 @@ public class PowerStatsExporterTest { mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 40000); mHistory.recordPowerStats(6000, 6000, powerStats); + mEnergyConsumerPowerStatsLayout.setConsumedEnergy(customPowerStats.stats, 0, 3_600_000); + mEnergyConsumerPowerStatsLayout.setUidConsumedEnergy(customUidStats, 0, 360_000); + mHistory.recordPowerStats(6010, 6010, customPowerStats); + mPowerStatsAggregator.aggregatePowerStats(3500, 6500, stats -> { mPowerStatsStore.storeAggregatedPowerStats(stats); }); @@ -271,20 +320,13 @@ public class PowerStatsExporterTest { exporter.exportAggregatedPowerStats(builder, monotonicStartTime, monotonicEndTime); } - private void assertDevicePowerEstimate(String message, BatteryUsageStats bus, int componentId, - double expected) { - AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); - assertWithMessage(message).that(consumer.getConsumedPower(componentId)) - .isWithin(TOLERANCE).of(expected); - } - - private void assertAllAppsPowerEstimate(String message, BatteryUsageStats bus, int componentId, - double expected) { - AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); - assertWithMessage(message).that(consumer.getConsumedPower(componentId)) - .isWithin(TOLERANCE).of(expected); + private void assertAggregatedPowerEstimate(String message, BatteryUsageStats bus, int scope, + int componentId, double expected) { + AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer(scope); + double actual = componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + ? consumer.getConsumedPower(componentId) + : consumer.getConsumedPowerForCustomComponent(componentId); + assertWithMessage(message).that(actual).isWithin(TOLERANCE).of(expected); } private void assertUidPowerEstimate(String message, BatteryUsageStats bus, int uid, @@ -293,9 +335,11 @@ public class PowerStatsExporterTest { final UidBatteryConsumer uidScope = uidScopes.stream() .filter(us -> us.getUid() == uid).findFirst().orElse(null); assertWithMessage(message).that(uidScope).isNotNull(); - assertWithMessage(message).that(uidScope.getConsumedPower( - new BatteryConsumer.Dimensions(componentId, processState))) - .isWithin(TOLERANCE).of(expected); + double actual = componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + ? uidScope.getConsumedPower( + new BatteryConsumer.Dimensions(componentId, processState)) + : uidScope.getConsumedPowerForCustomComponent(componentId); + assertWithMessage(message).that(actual).isWithin(TOLERANCE).of(expected); } private void clearDirectory(File dir) { 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 4e8c75559f3b..9884085cc3f7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -25,6 +25,8 @@ import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_D import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG; import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER; @@ -1082,7 +1084,7 @@ public class AccessibilityManagerServiceTest { mA11yms.enableShortcutsForTargets( /* enable= */ true, - UserShortcutType.HARDWARE, + HARDWARE, List.of(target), mA11yms.getCurrentUserIdLocked()); mTestableLooper.processAllMessages(); @@ -1346,14 +1348,14 @@ public class AccessibilityManagerServiceTest { mA11yms.enableShortcutsForTargets( /* enable= */ true, - UserShortcutType.HARDWARE, + HARDWARE, List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()), mA11yms.getCurrentUserIdLocked()); mTestableLooper.processAllMessages(); assertThat( ShortcutUtils.isComponentIdExistingInSettings( - mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE, + mTestableContext, HARDWARE, TARGET_STANDARD_A11Y_SERVICE.flattenToString()) ).isTrue(); } @@ -1367,7 +1369,7 @@ public class AccessibilityManagerServiceTest { mA11yms.enableShortcutsForTargets( /* enable= */ false, - UserShortcutType.HARDWARE, + HARDWARE, List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()), mA11yms.getCurrentUserIdLocked()); mTestableLooper.processAllMessages(); @@ -1375,7 +1377,7 @@ public class AccessibilityManagerServiceTest { assertThat( ShortcutUtils.isComponentIdExistingInSettings( mTestableContext, - ShortcutConstants.UserShortcutType.HARDWARE, + HARDWARE, TARGET_STANDARD_A11Y_SERVICE.flattenToString())) .isFalse(); } @@ -1390,14 +1392,14 @@ public class AccessibilityManagerServiceTest { mA11yms.enableShortcutsForTargets( /* enable= */ true, - UserShortcutType.QUICK_SETTINGS, + QUICK_SETTINGS, List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()), mA11yms.getCurrentUserIdLocked()); mTestableLooper.processAllMessages(); assertThat( ShortcutUtils.isComponentIdExistingInSettings( - mTestableContext, UserShortcutType.QUICK_SETTINGS, + mTestableContext, QUICK_SETTINGS, TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()) ).isTrue(); verify(mStatusBarManagerInternal) @@ -1417,14 +1419,14 @@ public class AccessibilityManagerServiceTest { mA11yms.enableShortcutsForTargets( /* enable= */ false, - UserShortcutType.QUICK_SETTINGS, + QUICK_SETTINGS, List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()), mA11yms.getCurrentUserIdLocked()); mTestableLooper.processAllMessages(); assertThat( ShortcutUtils.isComponentIdExistingInSettings( - mTestableContext, UserShortcutType.QUICK_SETTINGS, + mTestableContext, QUICK_SETTINGS, TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()) ).isFalse(); verify(mStatusBarManagerInternal) @@ -1614,44 +1616,49 @@ public class AccessibilityManagerServiceTest { @Test @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT) - public void restoreAccessibilityQsTargets_a11yQsTargetsRestored() { + public void restoreShortcutTargets_qs_a11yQsTargetsRestored() { String daltonizerTile = AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(); String colorInversionTile = AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString(); final AccessibilityUserState userState = new AccessibilityUserState( UserHandle.USER_SYSTEM, mTestableContext, mA11yms); - userState.updateA11yQsTargetLocked(Set.of(daltonizerTile)); + userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS); mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState); broadcastSettingRestored( - Settings.Secure.ACCESSIBILITY_QS_TARGETS, - /*previousValue=*/null, + ShortcutUtils.convertToKey(QUICK_SETTINGS), /*newValue=*/colorInversionTile); - assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM).getA11yQsTargets()) - .containsExactlyElementsIn(Set.of(daltonizerTile, colorInversionTile)); + Set<String> expected = Set.of(daltonizerTile, colorInversionTile); + assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS))) + .containsExactlyElementsIn(expected); + assertThat(userState.getShortcutTargetsLocked(QUICK_SETTINGS)) + .containsExactlyElementsIn(expected); } @Test @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT) - public void restoreAccessibilityQsTargets_a11yQsTargetsNotRestored() { + public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() { String daltonizerTile = AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(); String colorInversionTile = AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString(); final AccessibilityUserState userState = new AccessibilityUserState( UserHandle.USER_SYSTEM, mTestableContext, mA11yms); - userState.updateA11yQsTargetLocked(Set.of(daltonizerTile)); + userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS); + putShortcutSettingForUser(QUICK_SETTINGS, daltonizerTile, userState.mUserId); mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState); broadcastSettingRestored( - Settings.Secure.ACCESSIBILITY_QS_TARGETS, - /*previousValue=*/null, + ShortcutUtils.convertToKey(QUICK_SETTINGS), /*newValue=*/colorInversionTile); - assertThat(userState.getA11yQsTargets()) - .containsExactlyElementsIn(Set.of(daltonizerTile)); + Set<String> expected = Set.of(daltonizerTile); + assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS))) + .containsExactlyElementsIn(expected); + assertThat(userState.getShortcutTargetsLocked(QUICK_SETTINGS)) + .containsExactlyElementsIn(expected); } @Test @@ -1717,27 +1724,26 @@ public class AccessibilityManagerServiceTest { @Test @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE) - public void restoreA11yShortcutTargetService_targetsMerged() { + public void restoreShortcutTargets_hardware_targetsMerged() { + mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY); final String servicePrevious = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); final String otherPrevious = TARGET_MAGNIFICATION; - final String combinedPrevious = String.join(":", servicePrevious, otherPrevious); final String serviceRestored = TARGET_STANDARD_A11Y_SERVICE.flattenToString(); final AccessibilityUserState userState = new AccessibilityUserState( UserHandle.USER_SYSTEM, mTestableContext, mA11yms); mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState); setupShortcutTargetServices(userState); + mA11yms.enableShortcutsForTargets( + true, HARDWARE, List.of(servicePrevious, otherPrevious), userState.mUserId); broadcastSettingRestored( - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, - /*previousValue=*/combinedPrevious, + ShortcutUtils.convertToKey(HARDWARE), /*newValue=*/serviceRestored); final Set<String> expected = Set.of(servicePrevious, otherPrevious, serviceRestored); - assertThat(readStringsFromSetting( - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)) + assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE))) .containsExactlyElementsIn(expected); - assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM) - .getShortcutTargetsLocked(UserShortcutType.HARDWARE)) + assertThat(userState.getShortcutTargetsLocked(HARDWARE)) .containsExactlyElementsIn(expected); } @@ -1745,7 +1751,7 @@ public class AccessibilityManagerServiceTest { @EnableFlags({ android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE, Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE}) - public void restoreA11yShortcutTargetService_alreadyHadDefaultService_doesNotClear() { + public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() { final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString(); mTestableContext.getOrCreateTestableResources().addOverride( R.string.config_defaultAccessibilityService, serviceDefault); @@ -1754,17 +1760,18 @@ public class AccessibilityManagerServiceTest { mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState); setupShortcutTargetServices(userState); + // default is present in userState & setting, so it's not cleared + putShortcutSettingForUser(HARDWARE, serviceDefault, UserHandle.USER_SYSTEM); + userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE); + broadcastSettingRestored( Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, - /*previousValue=*/serviceDefault, /*newValue=*/serviceDefault); final Set<String> expected = Set.of(serviceDefault); - assertThat(readStringsFromSetting( - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)) + assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE))) .containsExactlyElementsIn(expected); - assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM) - .getShortcutTargetsLocked(UserShortcutType.HARDWARE)) + assertThat(userState.getShortcutTargetsLocked(HARDWARE)) .containsExactlyElementsIn(expected); } @@ -1772,7 +1779,7 @@ public class AccessibilityManagerServiceTest { @EnableFlags({ android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE, Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE}) - public void restoreA11yShortcutTargetService_didNotHaveDefaultService_clearsDefaultService() { + public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() { final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString(); final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); // Restored value from the broadcast contains both default and non-default service. @@ -1784,18 +1791,45 @@ public class AccessibilityManagerServiceTest { mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState); setupShortcutTargetServices(userState); - broadcastSettingRestored( - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, - /*previousValue=*/null, + broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE), /*newValue=*/combinedRestored); // The default service is cleared from the final restored value. final Set<String> expected = Set.of(serviceRestored); - assertThat(readStringsFromSetting( - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)) + assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE))) .containsExactlyElementsIn(expected); - assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM) - .getShortcutTargetsLocked(UserShortcutType.HARDWARE)) + assertThat(userState.getShortcutTargetsLocked(HARDWARE)) + .containsExactlyElementsIn(expected); + } + + @Test + @EnableFlags({ + android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE, + Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE}) + public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() { + final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString(); + final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); + // Restored value from the broadcast contains both default and non-default service. + final String combinedRestored = String.join(":", serviceDefault, serviceRestored); + mTestableContext.getOrCreateTestableResources().addOverride( + R.string.config_defaultAccessibilityService, serviceDefault); + final AccessibilityUserState userState = new AccessibilityUserState( + UserHandle.USER_SYSTEM, mTestableContext, mA11yms); + mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState); + setupShortcutTargetServices(userState); + + // UserState has default, but setting is null (this emulates a typical scenario in SUW). + userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE); + putShortcutSettingForUser(HARDWARE, null, UserHandle.USER_SYSTEM); + + broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE), + /*newValue=*/combinedRestored); + + // The default service is cleared from the final restored value. + final Set<String> expected = Set.of(serviceRestored); + assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE))) + .containsExactlyElementsIn(expected); + assertThat(userState.getShortcutTargetsLocked(HARDWARE)) .containsExactlyElementsIn(expected); } @@ -1806,11 +1840,10 @@ public class AccessibilityManagerServiceTest { return result; } - private void broadcastSettingRestored(String setting, String previousValue, String newValue) { + private void broadcastSettingRestored(String setting, String newValue) { Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) .putExtra(Intent.EXTRA_SETTING_NAME, setting) - .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue) .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, newValue); sendBroadcastToAccessibilityManagerService(intent); mTestableLooper.processAllMessages(); @@ -1952,4 +1985,13 @@ public class AccessibilityManagerServiceTest { private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) { return service.getCurrentUserIdLocked() == context.getUserId(); } + + private void putShortcutSettingForUser(@UserShortcutType int shortcutType, + String shortcutValue, int userId) { + Settings.Secure.putStringForUser( + mTestableContext.getContentResolver(), + ShortcutUtils.convertToKey(shortcutType), + shortcutValue, + userId); + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index b269beb90c75..9fad14d889fd 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -28,6 +28,7 @@ import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSI import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED; import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain; import static com.google.common.truth.Truth.assertThat; @@ -429,20 +430,20 @@ public class AccessibilityUserStateTest { } @Test - public void updateA11yQsTargetLocked_valueUpdated() { + public void updateShortcutTargetsLocked_quickSettings_valueUpdated() { Set<String> newTargets = Set.of( AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(), AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString() ); - mUserState.updateA11yQsTargetLocked(newTargets); + mUserState.updateShortcutTargetsLocked(newTargets, QUICK_SETTINGS); assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets); } @Test public void getA11yQsTargets_returnsCopiedData() { - updateA11yQsTargetLocked_valueUpdated(); + updateShortcutTargetsLocked_quickSettings_valueUpdated(); Set<String> targets = mUserState.getA11yQsTargets(); targets.clear(); diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 30e3b1808819..dbab54b76a2e 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -672,6 +672,61 @@ public class UserControllerTest { new HashSet<>(mUserController.getRunningUsersLU())); } + /** Test scheduling stopping of background users - reschedule if current user is a guest. */ + @Test + public void testScheduleStopOfBackgroundUser_rescheduleWhenGuest() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER); + + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ 2); + + final int TEST_USER_GUEST = 902; + setUpUser(TEST_USER_GUEST, UserInfo.FLAG_GUEST); + + setUpUser(TEST_USER_ID2, NO_USERINFO_FLAGS); + + // Switch to TEST_USER_ID from user 0 + int numberOfUserSwitches = 0; + addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, + ++numberOfUserSwitches, false, + /* expectScheduleBackgroundUserStopping= */ false); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID), + mUserController.getRunningUsersLU()); + + // Switch to TEST_USER_GUEST from TEST_USER_ID + addForegroundUserAndContinueUserSwitch(TEST_USER_GUEST, TEST_USER_ID, + ++numberOfUserSwitches, false, + /* expectScheduleBackgroundUserStopping= */ true); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_GUEST), + mUserController.getRunningUsersLU()); + + // Allow the post-switch processing to complete. + // TEST_USER_ID may be scheduled for stopping, but it shouldn't actually stop since the + // current user is a Guest. + assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID); + assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_GUEST); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_GUEST), + mUserController.getRunningUsersLU()); + + // Switch to TEST_USER_ID2 from TEST_USER_GUEST + // Guests are automatically stopped in the background, so it won't be scheduled. + addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_GUEST, + ++numberOfUserSwitches, true, + /* expectScheduleBackgroundUserStopping= */ false); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID2), + mUserController.getRunningUsersLU()); + + // Allow the post-switch processing to complete. + // TEST_USER_ID should *still* be scheduled for stopping, since we skipped stopping it + // earlier. + assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID); + assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_GUEST); + assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID2); + assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID2), + mUserController.getRunningUsersLU()); + } + /** * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected. * @param userId the user we are checking to see whether it is scheduled. @@ -682,11 +737,11 @@ public class UserControllerTest { boolean expectScheduled, @Nullable Integer userId) { TestHandler handler = mInjector.mHandler; if (expectScheduled) { - assertTrue(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId)); + assertTrue(handler.hasEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId)); handler.removeMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId); mUserController.processScheduledStopOfBackgroundUser(userId); } else { - assertFalse(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId)); + assertFalse(handler.hasEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId)); } } @@ -1534,9 +1589,9 @@ public class UserControllerTest { mInjector.mHandler.clearAllRecordedMessages(); // Verify that continueUserSwitch worked as expected continueAndCompleteUserSwitch(userState, oldUserId, newUserId); - assertEquals(mInjector.mHandler - .hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId), - expectScheduleBackgroundUserStopping); + assertEquals(expectScheduleBackgroundUserStopping, + mInjector.mHandler + .hasEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId)); verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any()); continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping, expectScheduleBackgroundUserStopping); @@ -1810,6 +1865,13 @@ public class UserControllerTest { } private static class TestHandler extends Handler { + /** + * Keeps an accessible copy of messages that were queued for us to query. + * + * WARNING: queued messages get added to this, but processed/removed messages to NOT + * automatically get removed. This can lead to confusing bugs. Maybe one day someone will + * fix this, but in the meantime, this is your warning. + */ private final List<Message> mMessages = new ArrayList<>(); TestHandler(Looper looper) { diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java index 8a7815e12c8b..c34e28f7ddf3 100644 --- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java @@ -47,7 +47,9 @@ import android.content.pm.ShortcutServiceInternal; import android.os.Handler; import android.os.UserHandle; import android.test.InstrumentationTestCase; +import android.util.ArraySet; import android.util.AtomicFile; +import android.util.SparseArray; import android.util.Xml; import android.widget.RemoteViews; @@ -67,10 +69,12 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Random; +import java.util.Set; import java.util.concurrent.CountDownLatch; /** @@ -388,6 +392,93 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { assertThat(target.previewLayout).isEqualTo(original.previewLayout); } + public void testBackupRestoreControllerStatePersistence() throws IOException { + // Setup mock data + final Set<String> mockPrunedApps = getMockPrunedApps(); + final SparseArray< + List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> + > mockUpdatesByProvider = getMockUpdates(); + final SparseArray< + List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> + > mockUpdatesByHost = getMockUpdates(); + final AppWidgetServiceImpl.BackupRestoreController.State state = + new AppWidgetServiceImpl.BackupRestoreController.State( + mockPrunedApps, mockUpdatesByProvider, mockUpdatesByHost); + + final File file = new File(mTestContext.getDataDir(), "state.xml"); + saveBackupRestoreControllerState(file, state); + final AppWidgetServiceImpl.BackupRestoreController.State target = + loadStateLocked(file); + assertNotNull(target); + final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> + actualUpdatesByProvider = target.getUpdatesByProvider(); + assertNotNull(actualUpdatesByProvider); + final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> + actualUpdatesByHost = target.getUpdatesByHost(); + assertNotNull(actualUpdatesByHost); + + assertEquals(mockPrunedApps, target.getPrunedApps()); + for (int i = 0; i < mockUpdatesByProvider.size(); i++) { + final int key = mockUpdatesByProvider.keyAt(i); + verifyRestoreUpdateRecord( + actualUpdatesByProvider.get(key), mockUpdatesByProvider.get(key)); + } + for (int i = 0; i < mockUpdatesByHost.size(); i++) { + final int key = mockUpdatesByHost.keyAt(i); + verifyRestoreUpdateRecord( + actualUpdatesByHost.get(key), mockUpdatesByHost.get(key)); + } + } + + private void verifyRestoreUpdateRecord( + @NonNull final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> + actualUpdates, + @NonNull final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> + expectedUpdates) { + assertEquals(expectedUpdates.size(), actualUpdates.size()); + for (int i = 0; i < expectedUpdates.size(); i++) { + final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord expected = + expectedUpdates.get(i); + final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord actual = + actualUpdates.get(i); + assertEquals(expected.oldId, actual.oldId); + assertEquals(expected.newId, actual.newId); + assertEquals(expected.notified, actual.notified); + } + } + + @NonNull + private static Set<String> getMockPrunedApps() { + final Set<String> mockPrunedApps = new ArraySet<>(10); + for (int i = 0; i < 10; i++) { + mockPrunedApps.add("com.example.app" + i); + } + return mockPrunedApps; + } + + @NonNull + private static SparseArray< + List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> + > getMockUpdates() { + final SparseArray<List< + AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> ret = + new SparseArray<>(4); + ret.put(0, new ArrayList<>()); + for (int i = 0; i < 5; i++) { + final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord record = + new AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord( + 5 - i, i); + record.notified = (i % 2 == 1); + final int key = (i < 3) ? 1 : 2; + if (!ret.contains(key)) { + ret.put(key, new ArrayList<>()); + } + ret.get(key).add(record); + } + ret.put(3, new ArrayList<>()); + return ret; + } + private int setupHostAndWidget() { List<PendingHostUpdate> updates = mService.startListening( mMockHost, mPkgName, HOST_ID, new int[0]).getList(); @@ -418,6 +509,40 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { return mTestContext.getResources().getInteger(resId); } + private static void saveBackupRestoreControllerState( + @NonNull final File dst, + @Nullable final AppWidgetServiceImpl.BackupRestoreController.State state) + throws IOException { + Objects.requireNonNull(dst); + if (state == null) { + return; + } + final AtomicFile file = new AtomicFile(dst); + final FileOutputStream stream = file.startWrite(); + final TypedXmlSerializer out = Xml.resolveSerializer(stream); + out.startDocument(null, true); + AppWidgetXmlUtil.writeBackupRestoreControllerState(out, state); + out.endDocument(); + file.finishWrite(stream); + } + + private static AppWidgetServiceImpl.BackupRestoreController.State loadStateLocked( + @NonNull final File dst) { + Objects.requireNonNull(dst); + final AtomicFile file = new AtomicFile(dst); + try (FileInputStream stream = file.openRead()) { + final TypedXmlPullParser parser = Xml.resolvePullParser(stream); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + // drain whitespace, comments, etc. + } + return AppWidgetXmlUtil.readBackupRestoreControllerState(parser); + } catch (IOException | XmlPullParserException e) { + return null; + } + } + private static void saveWidgetProviderInfoLocked(@NonNull final File dst, @Nullable final AppWidgetProviderInfo info) throws IOException { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 503ab8e60cac..0f385325e525 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -79,6 +79,9 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.security.GateKeeper; import android.security.KeyStoreAuthorization; @@ -118,6 +121,8 @@ public class BiometricServiceTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final String TEST_PACKAGE_NAME = "test_package"; private static final long TEST_REQUEST_ID = 44; @@ -1506,6 +1511,75 @@ public class BiometricServiceTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenMandatoryBiometricsRequested() + throws Exception { + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); + mBiometricService.onStart(); + + when(mTrustManager.isInSignificantPlace()).thenReturn(false); + when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + .thenReturn(true); + + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + + assertEquals(BiometricManager.BIOMETRIC_SUCCESS, + invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); + + when(mTrustManager.isInSignificantPlace()).thenReturn(true); + + assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, + invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenMandatoryBiometricsAndStrongAuthenticatorsRequested() + throws Exception { + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); + mBiometricService.onStart(); + + when(mTrustManager.isInSignificantPlace()).thenReturn(false); + when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + .thenReturn(true); + + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + + assertEquals(BiometricManager.BIOMETRIC_SUCCESS, + invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS + | Authenticators.BIOMETRIC_STRONG)); + + when(mTrustManager.isInSignificantPlace()).thenReturn(true); + + assertEquals(BiometricManager.BIOMETRIC_SUCCESS, + invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS + | Authenticators.BIOMETRIC_STRONG)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenMandatoryBiometricsRequestedAndDeviceCredentialAvailable() + throws Exception { + mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider); + mBiometricService.onStart(); + + when(mTrustManager.isInSignificantPlace()).thenReturn(false); + when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + .thenReturn(true); + + setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL); + + assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, + invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); + + when(mTrustManager.isInSignificantPlace()).thenReturn(true); + + assertEquals(BiometricManager.BIOMETRIC_SUCCESS, + invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS + | Authenticators.DEVICE_CREDENTIAL)); + } + + @Test public void testAuthenticatorActualStrength() { // Tuple of OEM config, updatedStrength, and expectedStrength final int[][] testCases = { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java index b40d7ee39a4c..b831ef5c30bc 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java @@ -32,11 +32,16 @@ import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; import android.content.Context; +import android.content.res.Resources; import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.PromptInfo; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.filters.SmallTest; @@ -54,6 +59,9 @@ import java.util.List; public class PreAuthInfoTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int SENSOR_ID_FINGERPRINT = 0; private static final int SENSOR_ID_FACE = 1; @@ -66,6 +74,8 @@ public class PreAuthInfoTest { @Mock Context mContext; @Mock + Resources mResources; + @Mock ITrustManager mTrustManager; @Mock DevicePolicyManager mDevicePolicyManager; @@ -80,6 +90,7 @@ public class PreAuthInfoTest { when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt())) .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE); when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); + when(mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())).thenReturn(true); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true); when(mFaceAuthenticator.getLockoutModeForUser(anyInt())) @@ -91,6 +102,8 @@ public class PreAuthInfoTest { .thenReturn(LOCKOUT_NONE); when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false); when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getString(anyInt())).thenReturn(TEST_PACKAGE_NAME); } @Test @@ -184,6 +197,54 @@ public class PreAuthInfoTest { assertThat(preAuthInfo.eligibleSensors.get(0).modality).isEqualTo(TYPE_FINGERPRINT); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testMandatoryBiometricsStatus_whenAllRequirementsSatisfiedAndSensorAvailable() + throws Exception { + when(mTrustManager.isInSignificantPlace()).thenReturn(false); + + final BiometricSensor sensor = getFaceSensor(); + final PromptInfo promptInfo = new PromptInfo(); + promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS); + final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, + mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME, + false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager); + + assertThat(preAuthInfo.eligibleSensors).hasSize(1); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testMandatoryBiometricsStatus_whenAllRequirementsSatisfiedAndSensorUnavailable() + throws Exception { + when(mTrustManager.isInSignificantPlace()).thenReturn(false); + + final PromptInfo promptInfo = new PromptInfo(); + promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS); + final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, + mSettingObserver, List.of(), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME, + false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager); + + assertThat(preAuthInfo.eligibleSensors).hasSize(0); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable() + throws Exception { + when(mTrustManager.isInSignificantPlace()).thenReturn(true); + + final BiometricSensor sensor = getFaceSensor(); + final PromptInfo promptInfo = new PromptInfo(); + promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS + | BiometricManager.Authenticators.BIOMETRIC_STRONG); + final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, + mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME, + false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager); + + assertThat(preAuthInfo.eligibleSensors).hasSize(1); + } + private BiometricSensor getFingerprintSensor() { BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT, TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index b05a819ba489..cb75e1ab6cce 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -179,7 +179,8 @@ public class UtilsTest { // The rest of the bits are not allowed to integrate with the public APIs for (int i = 8; i < 32; i++) { final int authenticator = 1 << i; - if (authenticator == Authenticators.DEVICE_CREDENTIAL) { + if (authenticator == Authenticators.DEVICE_CREDENTIAL + || authenticator == Authenticators.MANDATORY_BIOMETRICS) { continue; } assertFalse(Utils.isValidAuthenticatorConfig(1 << i)); diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java index c165c661a625..d8fd266ab37f 100644 --- a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java +++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java @@ -21,8 +21,10 @@ import android.content.pm.ApplicationInfo; class ApplicationInfoBuilder { private boolean mIsDebuggable; private int mTargetSdk; + private int mUid; private String mPackageName; private long mVersionCode; + private boolean mIsSystemApp; private ApplicationInfoBuilder() { mTargetSdk = -1; @@ -42,6 +44,16 @@ class ApplicationInfoBuilder { return this; } + ApplicationInfoBuilder systemApp() { + mIsSystemApp = true; + return this; + } + + ApplicationInfoBuilder withUid(int uid) { + mUid = uid; + return this; + } + ApplicationInfoBuilder withPackageName(String packageName) { mPackageName = packageName; return this; @@ -60,6 +72,10 @@ class ApplicationInfoBuilder { applicationInfo.packageName = mPackageName; applicationInfo.targetSdkVersion = mTargetSdk; applicationInfo.longVersionCode = mVersionCode; + applicationInfo.uid = mUid; + if (mIsSystemApp) { + applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; + } return applicationInfo; } } diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index 9accd49bc392..9df7a3612a92 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -29,6 +29,7 @@ import static org.testng.Assert.assertThrows; import android.compat.Compatibility.ChangeConfig; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Build; @@ -36,6 +37,7 @@ import android.os.Build; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.internal.compat.ChangeReporter; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; import com.android.server.LocalServices; @@ -65,6 +67,8 @@ public class PlatformCompatTest { CompatConfig mCompatConfig; @Mock private AndroidBuildClassifier mBuildClassifier; + @Mock + private ChangeReporter mChangeReporter; @Before public void setUp() throws Exception { @@ -78,7 +82,8 @@ public class PlatformCompatTest { when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) .thenThrow(new PackageManager.NameNotFoundException()); mCompatConfig = new CompatConfig(mBuildClassifier, mContext); - mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier); + mPlatformCompat = + new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter); // Assume userdebug/eng non-final build mCompatConfig.forceNonDebuggableFinalForTest(false); when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); @@ -100,7 +105,8 @@ public class PlatformCompatTest { .addLoggingOnlyChangeWithId(7L) .addDisabledOverridableChangeWithId(8L) .build(); - mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier); + mPlatformCompat = + new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter); assertThat(mPlatformCompat.listAllChanges()).asList().containsExactly( new CompatibilityChangeInfo(1L, "", -1, -1, false, false, "", false), new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, "", false), @@ -128,7 +134,8 @@ public class PlatformCompatTest { .addLoggingOnlyChangeWithId(7L) .addEnableSinceSdkChangeWithId(31, 8L) .build(); - mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier); + mPlatformCompat = + new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter); assertThat(mPlatformCompat.listUIChanges()).asList().containsExactly( new CompatibilityChangeInfo(1L, "", -1, -1, false, false, "", false), new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, "", false), @@ -146,7 +153,8 @@ public class PlatformCompatTest { .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.O, 3L) .build(); mCompatConfig.forceNonDebuggableFinalForTest(true); - mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier); + mPlatformCompat = + new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter); // Before adding overrides. assertThat(mPlatformCompat.isChangeEnabledByPackageName(1, PACKAGE_NAME, 0)).isTrue(); @@ -369,4 +377,68 @@ public class PlatformCompatTest { // Listener not called when a non existing override is removed. verify(mListener1, never()).onCompatChange(PACKAGE_NAME); } + + @Test + public void testReportChange() throws Exception { + ApplicationInfo appInfo = ApplicationInfoBuilder.create().withUid(123).build(); + mPlatformCompat.reportChange(1L, appInfo); + verify(mChangeReporter).reportChange(123, 1L, ChangeReporter.STATE_LOGGED, false, true); + + ApplicationInfo systemAppInfo = + ApplicationInfoBuilder.create().withUid(123).systemApp().build(); + mPlatformCompat.reportChange(1L, systemAppInfo); + verify(mChangeReporter).reportChange(123, 1L, ChangeReporter.STATE_LOGGED, true, true); + } + + @Test + public void testReportChangeByPackageName() throws Exception { + when(mPackageManagerInternal.getApplicationInfo( + eq(PACKAGE_NAME), eq(0L), anyInt(), anyInt())) + .thenReturn( + ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .withUid(123) + .build()); + + mPlatformCompat.reportChangeByPackageName(1L, PACKAGE_NAME, 123); + verify(mChangeReporter).reportChange(123, 1L, ChangeReporter.STATE_LOGGED, false, true); + + String SYSTEM_PACKAGE_NAME = "my.system.package"; + + when(mPackageManagerInternal.getApplicationInfo( + eq(SYSTEM_PACKAGE_NAME), eq(0L), anyInt(), anyInt())) + .thenReturn( + ApplicationInfoBuilder.create() + .withPackageName(SYSTEM_PACKAGE_NAME) + .withUid(123) + .systemApp() + .build()); + + mPlatformCompat.reportChangeByPackageName(1L, SYSTEM_PACKAGE_NAME, 123); + verify(mChangeReporter).reportChange(123, 1L, ChangeReporter.STATE_LOGGED, true, true); + } + + @Test + public void testIsChangeEnabled() throws Exception { + mCompatConfig = + CompatConfigBuilder.create(mBuildClassifier, mContext) + .addEnabledChangeWithId(1L) + .addDisabledChangeWithId(2L) + .addEnabledChangeWithId(3L) + .build(); + mCompatConfig.forceNonDebuggableFinalForTest(true); + mPlatformCompat = + new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter); + + ApplicationInfo appInfo = ApplicationInfoBuilder.create().withUid(123).build(); + assertThat(mPlatformCompat.isChangeEnabled(1L, appInfo)).isTrue(); + verify(mChangeReporter).reportChange(123, 1L, ChangeReporter.STATE_ENABLED, false, false); + assertThat(mPlatformCompat.isChangeEnabled(2L, appInfo)).isFalse(); + verify(mChangeReporter).reportChange(123, 2L, ChangeReporter.STATE_DISABLED, false, false); + + ApplicationInfo systemAppInfo = + ApplicationInfoBuilder.create().withUid(123).systemApp().build(); + assertThat(mPlatformCompat.isChangeEnabled(3L, systemAppInfo)).isTrue(); + verify(mChangeReporter).reportChange(123, 3L, ChangeReporter.STATE_ENABLED, true, false); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java index ddb9b43be76f..58a1d9c1b932 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java @@ -16,12 +16,15 @@ package com.android.server.notification; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import android.app.Application; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Intent; import android.net.Uri; import android.testing.AndroidTestingRunner; @@ -66,6 +69,12 @@ public class CountdownConditionProviderTest extends UiServiceTestCase { } @Test + public void getComponent_returnsComponent() { + assertThat(mService.getComponent()).isEqualTo(new ComponentName("android", + "com.android.server.notification.CountdownConditionProvider")); + } + + @Test public void testGetPendingIntent() { PendingIntent pi = mService.getPendingIntent(Uri.EMPTY); assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java index 4c440cabf9c4..4af96ef4800f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java @@ -16,14 +16,16 @@ package com.android.server.notification; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import android.app.Application; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Intent; -import android.net.Uri; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -63,7 +65,13 @@ public class EventConditionProviderTest extends UiServiceTestCase { service.onCreate(); service.onBind(startIntent); mService = spy(service); - } + } + + @Test + public void getComponent_returnsComponent() { + assertThat(mService.getComponent()).isEqualTo(new ComponentName("android", + "com.android.server.notification.EventConditionProvider")); + } @Test public void testGetPendingIntent() { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 398dc28121fa..c48d745b1dc0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -40,6 +40,7 @@ import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.Notification.VISIBILITY_PRIVATE; +import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; @@ -91,7 +92,9 @@ import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_TEXT_REPLIES; +import static android.service.notification.Adjustment.KEY_TYPE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; +import static android.service.notification.Adjustment.TYPE_NEWS; import static android.service.notification.Condition.SOURCE_CONTEXT; import static android.service.notification.Condition.SOURCE_USER_ACTION; import static android.service.notification.Condition.STATE_TRUE; @@ -14332,6 +14335,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN) + public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + NotificationRecord record = new NotificationRecord( + mContext, + new StatusBarNotification(mPkg, mPkg, 1, "tag", mUid, 44, receivedWithoutParceling, + mUser, "groupKey", 0), + mTestNotificationChannel); + assertThat(record.getNotification().getAllowlistToken()).isNull(); + + mWorkerHandler.post( + mService.new EnqueueNotificationRunnable(mUserId, record, false, false, + mPostNotificationTrackerFactory.newTracker(null))); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN) public void enqueueNotification_rejectsOtherToken() throws RemoteException { Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentIntent(createPendingIntent("content")) @@ -15778,4 +15804,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component); } + + @Test + @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public void testApplyAdjustment_keyType_validType() throws Exception { + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(r); + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + + Bundle signals = new Bundle(); + signals.putInt(KEY_TYPE, TYPE_NEWS); + Adjustment adjustment = new Adjustment( + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + mBinderService.applyAdjustmentFromAssistant(null, adjustment); + + waitForIdle(); + + r.applyAdjustments(); + + assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index 594d6f2ebb98..6a99731b77b3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -37,6 +37,7 @@ import android.os.Parcel; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.util.Log; +import android.util.proto.ProtoOutputStream; import android.view.LayoutInflater; import android.view.View; import android.widget.RemoteViews; @@ -105,7 +106,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { private static final ImmutableSet<Class<?>> UNUSABLE_TYPES = ImmutableSet.of(Consumer.class, IBinder.class, MediaSession.Token.class, Parcel.class, PrintWriter.class, Resources.Theme.class, View.class, - LayoutInflater.Factory2.class); + LayoutInflater.Factory2.class, ProtoOutputStream.class); // Maximum number of times we allow generating the same class recursively. // E.g. new RemoteViews.addView(new RemoteViews()) but stop there. 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 d1880d2f3528..ff4bc2158502 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -23,6 +23,10 @@ import static android.app.Notification.VISIBILITY_SECRET; import static android.app.NotificationChannel.ALLOW_BUBBLE_ON; import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE; +import static android.app.NotificationChannel.NEWS_ID; +import static android.app.NotificationChannel.PROMOTIONS_ID; +import static android.app.NotificationChannel.RECS_ID; +import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationChannel.USER_LOCKED_LIGHTS; @@ -47,6 +51,9 @@ import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; +import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION; +import static android.service.notification.Flags.notificationClassification; + import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; @@ -184,6 +191,7 @@ import java.util.concurrent.ThreadLocalRandom; @SmallTest @RunWith(ParameterizedAndroidJunit4.class) +@EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) public class PreferencesHelperTest extends UiServiceTestCase { private static final int UID_HEADLESS = 1000000; private static final UserHandle USER = UserHandle.of(0); @@ -233,7 +241,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return FlagsParameterization.allCombinationsOf( - FLAG_PERSIST_INCOMPLETE_RESTORE_DATA); + FLAG_NOTIFICATION_CLASSIFICATION); } public PreferencesHelperTest(FlagsParameterization flags) { @@ -582,6 +590,16 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false, UID_N_MR1, false)); + NotificationChannel updateNews = null; + if (notificationClassification()) { + // change one of the reserved bundle channels to ensure changes are persisted across + // boot + updateNews = mHelper.getNotificationChannel( + PKG_N_MR1, UID_N_MR1, NEWS_ID, false).copy(); + updateNews.setImportance(IMPORTANCE_NONE); + mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews, true, 1000, true); + } + mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false, @@ -597,6 +615,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false)); compareChannels(channel2, mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false)); + if (notificationClassification()) { + assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews.getId(), + false)).isNotNull(); + assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews.getId(), + false)).isEqualTo(updateNews); + } List<NotificationChannelGroup> actualGroups = mXmlHelper.getNotificationChannelGroups( PKG_N_MR1, UID_N_MR1, false, true, false, true, null).getList(); @@ -1130,9 +1154,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" " + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble" + "=\"false\" uid=\"10002\">\n" + + (notificationClassification() ? "<channel id=\"android.app.social\" " + + "name=\"Social\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"id\" name=\"name\" importance=\"2\" " + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" " + + "importance=\"2\" sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "</package>\n" + "<package name=\"com.example.n_mr1\" show_badge=\"true\" " + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" " @@ -1140,6 +1177,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "=\"false\" uid=\"10001\">\n" + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n" + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.social\" " + + "name=\"Social\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" " + "orig_imp=\"4\" />\n" + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" " @@ -1149,14 +1190,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" " + "orig_imp=\"4\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" " + + "importance=\"2\" sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"miscellaneous\" name=\"Uncategorized\" " + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + "</package>\n" + "<package name=\"com.example.p\" show_badge=\"true\" " + "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\"" - + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" uid=\"10003\" />\n" - + "</ranking>"; + + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" uid=\"10003\""; assertThat(baos.toString()).contains(expected); } @@ -1216,9 +1265,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" " + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble" + "=\"false\">\n" + + (notificationClassification() ? "<channel id=\"android.app.social\" " + + "name=\"Social\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"id\" name=\"name\" importance=\"2\" " + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" " + + "importance=\"2\" sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "</package>\n" // Importance default because on in permission helper + "<package name=\"com.example.n_mr1\" importance=\"3\" show_badge=\"true\" " @@ -1227,6 +1289,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "=\"false\">\n" + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n" + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.social\" " + + "name=\"Social\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" " + "orig_imp=\"4\" />\n" + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" " @@ -1236,6 +1302,15 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" " + "orig_imp=\"4\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" " + + "importance=\"2\" sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"miscellaneous\" name=\"Uncategorized\" " + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" @@ -1243,12 +1318,11 @@ public class PreferencesHelperTest extends UiServiceTestCase { // Importance default because on in permission helper + "<package name=\"com.example.p\" importance=\"3\" show_badge=\"true\" " + "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\"" - + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" />\n" - // Packages that exist solely in permissionhelper - + "<package name=\"first\" importance=\"3\" />\n" - + "<package name=\"third\" importance=\"0\" />\n" - + "</ranking>"; + + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\""; assertThat(baos.toString()).contains(expected); + // Packages that exist solely in permissionhelper + assertThat(baos.toString()).contains("<package name=\"first\" importance=\"3\""); + assertThat(baos.toString()).contains("<package name=\"third\" importance=\"0\""); } @Test @@ -1303,9 +1377,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" " + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble" + "=\"false\">\n" + + (notificationClassification() ? "<channel id=\"android.app.social\" " + + "name=\"Social\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"id\" name=\"name\" importance=\"2\" " + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" " + + "importance=\"2\" sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "</package>\n" // Importance 0 because missing from permission helper + "<package name=\"com.example.n_mr1\" importance=\"0\" show_badge=\"true\" " @@ -1314,6 +1401,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "=\"false\">\n" + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n" + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.social\" " + + "name=\"Social\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" " + "orig_imp=\"4\" />\n" + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" " @@ -1323,6 +1414,15 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" " + "orig_imp=\"4\" />\n" + + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" " + + "importance=\"2\" sound=\"content://settings/system/notification_sound\" " + + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" " + + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "") + "<channel id=\"miscellaneous\" name=\"Uncategorized\" " + "sound=\"content://settings/system/notification_sound\" usage=\"5\" " + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" @@ -1330,8 +1430,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // Importance default because on in permission helper + "<package name=\"com.example.p\" importance=\"3\" show_badge=\"true\" " + "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\"" - + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" />\n" - + "</ranking>"; + + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\""; assertThat(baos.toString()).contains(expected); } @@ -1851,8 +1950,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { parser.nextTag(); mHelper.readXml(parser, false, UserHandle.USER_ALL); - final NotificationChannel updated1 = - mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false); + final NotificationChannel updated1 = mHelper.getNotificationChannel( + PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false); assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance()); assertTrue(updated1.canBypassDnd()); assertEquals(VISIBILITY_SECRET, updated1.getLockscreenVisibility()); @@ -2392,18 +2491,20 @@ public class PreferencesHelperTest extends UiServiceTestCase { // Returns only non-deleted channels List<NotificationChannel> channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList(); - assertEquals(2, channels.size()); // Default channel + non-deleted channel + // Default channel + non-deleted channel + system defaults + assertEquals(notificationClassification() ? 6 : 2, channels.size()); for (NotificationChannel nc : channels) { - if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) { + if (channel2.getId().equals(nc.getId())) { compareChannels(channel2, nc); } } // Returns deleted channels too channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList(); - assertEquals(3, channels.size()); // Includes default channel + // Includes system channel(s) + assertEquals(notificationClassification() ? 7 : 3, channels.size()); for (NotificationChannel nc : channels) { - if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) { + if (channel2.getId().equals(nc.getId())) { compareChannels(channelMap.get(nc.getId()), nc); } } @@ -3013,7 +3114,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { final ApplicationInfo legacy = new ApplicationInfo(); legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1; - when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(legacy); + when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())) + .thenReturn(legacy); // create records with the default channel for all user 0 and user 1 uids mHelper.canShowBadge(PKG_N_MR1, user0Uids[i]); @@ -3024,13 +3126,14 @@ public class PreferencesHelperTest extends UiServiceTestCase { // user 0 records remain for (int i = 0; i < user0Uids.length; i++) { - assertEquals(1, - mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false).getList().size()); + assertEquals(notificationClassification() ? 5 : 1, + mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false) + .getList().size()); } // user 1 records are gone for (int i = 0; i < user1Uids.length; i++) { - assertEquals(0, - mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false).getList().size()); + assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false) + .getList().size()); } } @@ -3054,7 +3157,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{UID_N_MR1})); - assertEquals(2, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); + assertEquals(notificationClassification() ? 6 : 2, + mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); } @Test @@ -3126,7 +3230,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testRecordDefaults() throws Exception { assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); - assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); + assertEquals(notificationClassification() ? 5 : 1, + mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); } @Test @@ -3212,7 +3317,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(3, actual.size()); for (NotificationChannelGroup group : actual) { if (group.getId() == null) { - assertEquals(2, group.getChannels().size()); // misc channel too + assertEquals( + notificationClassification() ? 6 : 2, + group.getChannels().size()); // misc channel too assertTrue(channel3.getId().equals(group.getChannels().get(0).getId()) || channel3.getId().equals(group.getChannels().get(1).getId())); } else if (group.getId().equals(ncg.getId())) { @@ -3363,6 +3470,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false, UID_N_MR1, false); } + if (notificationClassification()) { + numChannels += 4; + } expectedChannels.put(pkgName, numChannels); } @@ -4583,7 +4693,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testTooManyChannels() { - for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) { + int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT; + if (notificationClassification()) { + // reserved channels lower limit + numToCreate -= 4; + } + for (int i = 0; i < numToCreate; i++) { NotificationChannel channel = new NotificationChannel(String.valueOf(i), String.valueOf(i), NotificationManager.IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true, UID_O, false); @@ -4602,11 +4717,16 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testTooManyChannels_xml() throws Exception { + int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT; + if (notificationClassification()) { + // reserved channels lower limit + numToCreate -= 4; + } String extraChannel = "EXTRA"; String extraChannel1 = "EXTRA1"; // create first... many... directly so we don't need a big xml blob in this test - for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) { + for (int i = 0; i < numToCreate; i++) { NotificationChannel channel = new NotificationChannel(String.valueOf(i), String.valueOf(i), NotificationManager.IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true, UID_O, false); @@ -5619,8 +5739,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - // number of channels with preferences should be 3 total - assertEquals("expected number of events", 3, events.size()); + assertEquals("expected number of events", + notificationClassification() ? 7 : 3, + events.size()); for (StatsEvent ev : events) { // all of these events should be of PackageNotificationChannelPreferences type, // and therefore we expect the atom to have this field. @@ -5643,7 +5764,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } else if (eventChannelId.equals("a")) { assertEquals("channel name", "a", p.getChannelName()); assertEquals("importance", IMPORTANCE_LOW, p.getImportance()); - } else { // b + } else if (eventChannelId.equals("b")){ // b assertEquals("channel name", "b", p.getChannelName()); assertEquals("importance", IMPORTANCE_HIGH, p.getImportance()); } @@ -5661,11 +5782,17 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_O, UID_O, channelC, true, false, UID_O, false); List<String> channels = new LinkedList<>(Arrays.asList("a", "b", "c")); + if (notificationClassification()) { + channels.add(NEWS_ID); + channels.add(PROMOTIONS_ID); + channels.add(SOCIAL_MEDIA_ID); + channels.add(RECS_ID); + } ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - assertEquals("total events", 3, events.size()); + assertEquals("total events", notificationClassification() ? 7 : 3, events.size()); for (StatsEvent ev : events) { AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); assertTrue(atom.hasPackageNotificationChannelPreferences()); @@ -5699,7 +5826,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.pullPackageChannelPreferencesStats(events); // In this case, we want to check the properties of the conversation channel (not parent) - assertEquals("total events", 2, events.size()); + assertEquals("total events", notificationClassification() ? 6 : 2, events.size()); for (StatsEvent ev : events) { AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); assertTrue(atom.hasPackageNotificationChannelPreferences()); @@ -5731,7 +5858,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - assertEquals("total events", 2, events.size()); + assertEquals("total events", + notificationClassification() ? 6 : 2, + events.size()); for (StatsEvent ev : events) { AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); assertTrue(atom.hasPackageNotificationChannelPreferences()); @@ -5762,7 +5891,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); - assertEquals("total events", 2, events.size()); + assertEquals("total events", + notificationClassification() ? 6 : 2, + events.size()); for (StatsEvent ev : events) { AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev); assertTrue(atom.hasPackageNotificationChannelPreferences()); @@ -6044,6 +6175,23 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(0, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count()); } + @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testNotificationBundles() { + // do something that triggers settings creation for an app + mHelper.setShowBadge(PKG_O, UID_O, true); + + // verify 4 reserved channels are created + assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance()) + .isEqualTo(IMPORTANCE_LOW); + assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, false) + .getImportance()).isEqualTo(IMPORTANCE_LOW); + assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false) + .getImportance()).isEqualTo(IMPORTANCE_LOW); + assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, false).getImportance()) + .isEqualTo(IMPORTANCE_LOW); + } + private static NotificationChannel cloneChannel(NotificationChannel original) { Parcel parcel = Parcel.obtain(); try { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java index 7d89d87b6efe..fe4ce465e9be 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java @@ -1,5 +1,7 @@ package com.android.server.notification; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -8,6 +10,7 @@ import static org.mockito.Mockito.spy; import android.app.Application; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Intent; import android.net.Uri; import android.service.notification.Condition; @@ -58,6 +61,12 @@ public class ScheduleConditionProviderTest extends UiServiceTestCase { } @Test + public void getComponent_returnsComponent() { + assertThat(mService.getComponent()).isEqualTo(new ComponentName("android", + "com.android.server.notification.ScheduleConditionProvider")); + } + + @Test public void testIsValidConditionId_incomplete() throws Exception { Uri badConditionId = Uri.EMPTY; assertFalse(mService.isValidConditionId(badConditionId)); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 9352c1287ee1..f5ab95c1ab4c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -511,6 +511,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); + if (Flags.modesUi()) { + rule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_USER; + } Parcel parcel = Parcel.obtain(); rule.writeToParcel(parcel, 0); @@ -540,6 +543,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.triggerDescription, parceled.triggerDescription); assertEquals(rule.zenPolicy, parceled.zenPolicy); assertEquals(rule.deletionInstant, parceled.deletionInstant); + if (Flags.modesUi()) { + assertEquals(rule.disabledOrigin, parceled.disabledOrigin); + } assertEquals(rule, parceled); assertEquals(rule.hashCode(), parceled.hashCode()); @@ -620,6 +626,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); + if (Flags.modesUi()) { + rule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_APP; + } ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeRuleXml(rule, baos); @@ -653,6 +662,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.triggerDescription, fromXml.triggerDescription); assertEquals(rule.iconResName, fromXml.iconResName); assertEquals(rule.deletionInstant, fromXml.deletionInstant); + if (Flags.modesUi()) { + assertEquals(rule.disabledOrigin, fromXml.disabledOrigin); + } } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index 26a13cb47563..57587f7dc38a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import static android.app.Flags.FLAG_MODES_API; import static android.app.Flags.FLAG_MODES_UI; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +33,7 @@ import android.app.Flags; import android.app.NotificationManager; import android.content.ComponentName; import android.net.Uri; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -79,16 +81,17 @@ public class ZenModeDiffTest extends UiServiceTestCase { : Set.of("version", "manualRule", "automaticRules"); // Differences for flagged fields are only generated if the flag is enabled. - // "Metadata" fields (userModifiedFields & co, deletionInstant) are not compared. + // "Metadata" fields (userModifiedFields, deletionInstant, disabledOrigin) are not compared. private static final Set<String> ZEN_RULE_EXEMPT_FIELDS = android.app.Flags.modesApi() ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields", - "zenDeviceEffectsUserModifiedFields", "deletionInstant") + "zenDeviceEffectsUserModifiedFields", "deletionInstant", + "disabledOrigin") : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION, RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL, RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields", "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields", - "deletionInstant"); + "deletionInstant", "disabledOrigin"); // allowPriorityChannels is flagged by android.app.modes_api public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS = @@ -201,8 +204,8 @@ public class ZenModeDiffTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MODES_API) public void testConfigDiff_fieldDiffs_flagOn() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); // these two start the same ZenModeConfig c1 = new ZenModeConfig(); ZenModeConfig c2 = new ZenModeConfig(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 201b2868b3b8..7bb633e6e1e0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -43,7 +43,6 @@ import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED; -import static android.app.NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND; import static android.app.NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -5013,6 +5012,48 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void updateAutomaticZenRule_changeOwnerForSystemRule_allowed() { + when(mContext.checkCallingPermission(anyString())) + .thenReturn(PackageManager.PERMISSION_GRANTED); + AutomaticZenRule original = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName("android", "some.old.cps")) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule("android", original, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID); + + AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName("android", "brand.new.cps")) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, update, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(result).isNotNull(); + assertThat(result.getOwner().getClassName()).isEqualTo("brand.new.cps"); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void updateAutomaticZenRule_changeOwnerForAppOwnedRule_ignored() { + AutomaticZenRule original = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mContext.getPackageName(), "old.third.party.cps")) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), original, + UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID); + + AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mContext.getPackageName(), "new.third.party.cps")) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, update, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(result).isNotNull(); + assertThat(result.getOwner().getClassName()).isEqualTo("old.third.party.cps"); + } + + @Test @EnableFlags(FLAG_MODES_API) public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() { mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); @@ -6185,6 +6226,101 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(mZenModeHelper.getConfig().manualRule.zenDeviceEffects).isEqualTo(effects); } + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void addAutomaticZenRule_startsDisabled_recordsDisabledOrigin() { + AutomaticZenRule startsDisabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mPkg, "SomeProvider")) + .setEnabled(false) + .build(); + + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsDisabled, UPDATE_ORIGIN_APP, + "new", CUSTOM_PKG_UID); + + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_APP); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void updateAutomaticZenRule_disabling_recordsDisabledOrigin() { + AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mPkg, "SomeProvider")) + .setEnabled(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP, + "new", CUSTOM_PKG_UID); + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_UNKNOWN); + + AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled) + .setEnabled(false) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off", + Process.SYSTEM_UID); + + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_USER); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void updateAutomaticZenRule_keepingDisabled_preservesPreviousDisabledOrigin() { + AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mPkg, "SomeProvider")) + .setEnabled(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP, + "new", CUSTOM_PKG_UID); + AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled) + .setEnabled(false) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off", + Process.SYSTEM_UID); + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_USER); + + // Now update it again, for an unrelated reason with a different origin. + AutomaticZenRule nowRenamed = new AutomaticZenRule.Builder(nowDisabled) + .setName("Fancy pants rule") + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowRenamed, UPDATE_ORIGIN_APP, "update", + CUSTOM_PKG_UID); + + // Identity of the disabler is preserved. + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_USER); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void updateAutomaticZenRule_enabling_clearsDisabledOrigin() { + AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mPkg, "SomeProvider")) + .setEnabled(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP, + "new", CUSTOM_PKG_UID); + AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled) + .setEnabled(false) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off", + Process.SYSTEM_UID); + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_USER); + + // Now enable it again + AutomaticZenRule nowEnabled = new AutomaticZenRule.Builder(nowDisabled) + .setEnabled(true) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowEnabled, UPDATE_ORIGIN_APP, "on", + CUSTOM_PKG_UID); + + // Identity of the disabler was cleared. + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_UNKNOWN); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index d6c0fef9649a..18752847910b 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -781,6 +781,34 @@ public class VibratorManagerServiceTest { } @Test + @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS) + public void vibrate_thenDeniedAppOps_getsCancelled() throws Throwable { + mockVibrators(1); + VibratorManagerService service = createSystemReadyService(); + + var vib = vibrate(service, + VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), RINGTONE_ATTRS); + + assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); + + when(mAppOpsManagerMock.checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), + eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString())) + .thenReturn(AppOpsManager.MODE_IGNORED); + + service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null); + + assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS)); + + var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class); + verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS)) + .writeVibrationReportedAsync(statsInfoCaptor.capture()); + + VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0); + assertEquals(Vibration.Status.CANCELLED_BY_APP_OPS.getProtoEnumValue(), + touchMetrics.status); + } + + @Test public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() { VibratorManagerService service = createSystemReadyService(); 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 e91fd3794a48..44cabac586f2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1639,6 +1639,15 @@ public class ActivityRecordTests extends WindowTestsBase { clearInvocations(mDefaultDisplay); } + @Test + public void testCompleteResume_updateCompatDisplayInsets() { + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + doReturn(true).when(activity).shouldCreateCompatDisplayInsets(); + activity.setState(RESUMED, "test"); + activity.completeResumeLocked(); + assertNotNull(activity.getCompatDisplayInsets()); + } + /** * Verify destroy activity request completes successfully. */ @@ -3089,6 +3098,30 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test + public void testOnStartingWindowDrawn() { + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + // The task-has-been-visible should not affect the decision of making transition ready. + activity.getTask().setHasBeenVisible(true); + activity.detachFromProcess(); + activity.mStartingData = mock(StartingData.class); + registerTestTransitionPlayer(); + final Transition transition = activity.mTransitionController.requestTransitionIfNeeded( + WindowManager.TRANSIT_OPEN, 0 /* flags */, null /* trigger */, mDisplayContent); + activity.onStartingWindowDrawn(); + assertTrue(activity.mStartingData.mIsDisplayed); + // The transition can be ready by the starting window of a visible-requested activity + // without a running process. + assertTrue(transition.allReady()); + + // If other event makes the transition unready, the reentrant of onStartingWindowDrawn + // should not replace the readiness again. + transition.setReady(mDisplayContent, false); + activity.onStartingWindowDrawn(); + assertFalse(transition.allReady()); + } + + + @Test public void testCloseToSquareFixedOrientation() { if (Flags.insetsDecoupledConfiguration()) { // No test needed as decor insets no longer affects orientation. diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationCapabilityTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationCapabilityTest.java new file mode 100644 index 000000000000..f1cf866df4a5 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationCapabilityTest.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.AppCompatOrientationCapability.OrientationCapabilityState.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; +import static com.android.server.wm.AppCompatOrientationCapability.OrientationCapabilityState.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; + +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; + +import com.android.server.wm.utils.TestComponentStack; + +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.function.LongSupplier; + +/** + * Test class for {@link AppCompatOrientationCapability}. + * <p> + * Build/Install/Run: + * atest WmTests:AppCompatOrientationCapabilityTest + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class AppCompatOrientationCapabilityTest extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(true); + + robot.checkShouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() { + runTestScenario((robot) -> { + robot.prepareIsCameraCompatTreatmentEnabled(true); + robot.prepareIsCameraCompatTreatmentEnabledAtBuildTime(true); + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + + robot.createActivityWithComponentInNewTask(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(false); + robot.prepareIsTreatmentEnabledForTopActivity(true); + + robot.checkShouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + + robot.createActivityWithComponent(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(true); + + robot.checkShouldNotIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.enableProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); + + robot.createActivityWithComponent(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(true); + + robot.checkShouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse() + throws Exception { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.disableProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); + + robot.createActivityWithComponent(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(true); + + robot.checkShouldNotIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + public void testShouldIgnoreOrientationRequestLoop_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkRequestLoopExtended((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ 0); + }); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_propertyIsFalseAndOverride_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.disableProperty( + PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkRequestLoopExtended((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ 0); + }); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_isLetterboxed_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(true); + + robot.checkRequestLoopExtended((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ i); + }); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_noLoop_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ 0); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_timeout_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.prepareMockedTime(); + robot.checkRequestLoopExtended((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ 0); + robot.delay(); + }); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_returnsTrue() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkRequestLoop((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ i); + }); + robot.checkShouldIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) + public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkShouldNotIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<OrientationCapabilityRobotTest> consumer) { + spyOn(mWm.mLetterboxConfiguration); + final OrientationCapabilityRobotTest robot = + new OrientationCapabilityRobotTest(mWm, mAtm, mSupervisor); + consumer.accept(robot); + } + + private static class OrientationCapabilityRobotTest { + + @NonNull + private final ActivityTaskManagerService mAtm; + @NonNull + private final WindowManagerService mWm; + @NonNull + private final ActivityTaskSupervisor mSupervisor; + @NonNull + private final LetterboxConfiguration mLetterboxConfiguration; + @NonNull + private final TestComponentStack<ActivityRecord> mActivityStack; + @NonNull + private final TestComponentStack<Task> mTaskStack; + @NonNull + private final CurrentTimeMillisSupplierTest mTestCurrentTimeMillisSupplier; + + + OrientationCapabilityRobotTest(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor) { + mAtm = atm; + mWm = wm; + mSupervisor = supervisor; + mActivityStack = new TestComponentStack<>(); + mTaskStack = new TestComponentStack<>(); + mLetterboxConfiguration = mWm.mLetterboxConfiguration; + mTestCurrentTimeMillisSupplier = new CurrentTimeMillisSupplierTest(); + } + + void prepareRelaunchingAfterRequestedOrientationChanged(boolean enabled) { + getTopOrientationCapability().setRelaunchingAfterRequestedOrientationChanged(enabled); + } + + void prepareIsPolicyForIgnoringRequestedOrientationEnabled(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + } + + void prepareIsCameraCompatTreatmentEnabled(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); + } + + void prepareIsCameraCompatTreatmentEnabledAtBuildTime(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabledAtBuildTime(); + } + + void prepareIsTreatmentEnabledForTopActivity(boolean enabled) { + final DisplayRotationCompatPolicy displayPolicy = mActivityStack.top() + .mDisplayContent.mDisplayRotationCompatPolicy; + spyOn(displayPolicy); + doReturn(enabled).when(displayPolicy) + .isTreatmentEnabledForActivity(eq(mActivityStack.top())); + } + + // Useful to reduce timeout during tests + void prepareMockedTime() { + getTopOrientationCapability().mOrientationCapabilityState.mCurrentTimeMillisSupplier = + mTestCurrentTimeMillisSupplier; + } + + void delay() { + mTestCurrentTimeMillisSupplier.delay(SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS); + } + + void enableProperty(@NonNull String propertyName) { + setPropertyValue(propertyName, /* enabled */ true); + } + + void disableProperty(@NonNull String propertyName) { + setPropertyValue(propertyName, /* enabled */ false); + } + + void prepareIsLetterboxedForFixedOrientationAndAspectRatio(boolean enabled) { + spyOn(mActivityStack.top()); + doReturn(enabled).when(mActivityStack.top()) + .isLetterboxedForFixedOrientationAndAspectRatio(); + } + + void createActivityWithComponent() { + createActivityWithComponentInNewTask(/* inNewTask */ mTaskStack.isEmpty()); + } + + void createActivityWithComponentInNewTask() { + createActivityWithComponentInNewTask(/* inNewTask */ true); + } + + private void createActivityWithComponentInNewTask(boolean inNewTask) { + if (inNewTask) { + createNewTask(); + } + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setOnTop(true) + .setTask(mTaskStack.top()) + // Set the component to be that of the test class in order + // to enable compat changes + .setComponent(ComponentName.createRelative(mAtm.mContext, + com.android.server.wm.LetterboxUiControllerTest.class.getName())) + .build(); + mActivityStack.push(activity); + } + + void checkShouldIgnoreRequestedOrientation( + @Configuration.Orientation int expectedOrientation) { + assertTrue(getTopOrientationCapability() + .shouldIgnoreRequestedOrientation(expectedOrientation)); + } + + void checkShouldNotIgnoreRequestedOrientation( + @Configuration.Orientation int expectedOrientation) { + assertFalse(getTopOrientationCapability() + .shouldIgnoreRequestedOrientation(expectedOrientation)); + } + + void checkExpectedLoopCount(int expectedCount) { + assertEquals(expectedCount, getTopOrientationCapability() + .getSetOrientationRequestCounter()); + } + + void checkShouldNotIgnoreOrientationLoop() { + assertFalse(getTopOrientationCapability().shouldIgnoreOrientationRequestLoop()); + } + + void checkShouldIgnoreOrientationLoop() { + assertTrue(getTopOrientationCapability().shouldIgnoreOrientationRequestLoop()); + } + + void checkRequestLoop(IntConsumer consumer) { + for (int i = 0; i < MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { + consumer.accept(i); + } + } + + void checkRequestLoopExtended(IntConsumer consumer) { + for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { + consumer.accept(i); + } + } + + private AppCompatOrientationCapability getTopOrientationCapability() { + return mActivityStack.top().mAppCompatController.getAppCompatCapability() + .getAppCompatOrientationCapability(); + } + + private void createNewTask() { + final DisplayContent displayContent = new TestDisplayContent + .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); + final Task newTask = new TaskBuilder(mSupervisor).setDisplay(displayContent).build(); + mTaskStack.push(newTask); + } + + private void setPropertyValue(@NonNull String propertyName, boolean enabled) { + PackageManager.Property property = new PackageManager.Property(propertyName, + /* value */ enabled, /* packageName */ "", + /* className */ ""); + PackageManager pm = mWm.mContext.getPackageManager(); + spyOn(pm); + try { + doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); + } catch (PackageManager.NameNotFoundException e) { + fail(e.getLocalizedMessage()); + } + } + + private static class CurrentTimeMillisSupplierTest implements LongSupplier { + + private long mCurrenTimeMillis = System.currentTimeMillis(); + + @Override + public long getAsLong() { + return mCurrenTimeMillis; + } + + public void delay(long delay) { + mCurrenTimeMillis += delay; + } + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java new file mode 100644 index 000000000000..22609990cfeb --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER; +import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; +import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; +import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; + +import com.android.server.wm.utils.TestComponentStack; + +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +/** + * Test class for {@link AppCompatOrientationPolicy}. + * <p> + * Build/Install/Run: + * atest WmTests:AppCompatOrientationPolicyTest + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class AppCompatOrientationPolicyTest extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + public void testOverrideOrientationIfNeeded_mapInvokedOnRequest() { + runTestScenarioWithActivity((robot) -> { + robot.overrideOrientationIfNeeded(SCREEN_ORIENTATION_PORTRAIT); + robot.checkOrientationRequestMapped(); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) + public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUser() { + runTestScenarioWithActivity((robot) -> { + robot.configureSetIgnoreOrientationRequest(true); + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_USER); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) + public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_optOut_isUnchanged() { + runTestScenario((robot) -> { + robot.disableProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); + robot.createActivityWithComponent(); + robot.configureSetIgnoreOrientationRequest(true); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) + public void testOverrideOrientationIfNeeded_fullscreenOverrides_optOutSystem_returnsUser() { + runTestScenario((robot) -> { + robot.disableProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); + robot.configureIsUserAppAspectRatioFullscreenEnabled(true); + + robot.createActivityWithComponent(); + robot.configureSetIgnoreOrientationRequest(true); + robot.prepareGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_USER); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) + public void testOverrideOrientationIfNeeded_fullscreenOverrides_optOutUser_returnsUser() { + runTestScenario((robot) -> { + robot.disableProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE); + robot.configureIsUserAppAspectRatioFullscreenEnabled(true); + + robot.createActivityWithComponent(); + robot.configureSetIgnoreOrientationRequest(true); + robot.prepareGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_USER); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) + public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUnchanged() + throws Exception { + runTestScenarioWithActivity((robot) -> { + robot.configureSetIgnoreOrientationRequest(false); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) + public void testOverrideOrientationIfNeeded_fullscreenAndUserOverrideEnabled_isUnchanged() { + runTestScenario((robot) -> { + robot.prepareIsUserAppAspectRatioSettingsEnabled(true); + + robot.createActivityWithComponent(); + robot.configureSetIgnoreOrientationRequest(true); + robot.prepareGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait() { + runTestScenarioWithActivity((robot) -> { + robot.checkOverrideOrientation( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) + public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() { + runTestScenarioWithActivity((robot) -> { + robot.checkOverrideOrientation( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_NOSENSOR); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) + public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_isUnchanged() { + runTestScenarioWithActivity((robot) -> { + robot.checkOverrideOrientation( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) + public void testOverrideOrientationIfNeeded_reverseLandscape_portraitOrUndefined_isUnchanged() { + runTestScenarioWithActivity((robot) -> { + robot.checkOverrideOrientation( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + robot.checkOverrideOrientation( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) + public void testOverrideOrientationIfNeeded_reverseLandscape_Landscape_getsReverseLandscape() { + runTestScenarioWithActivity((robot) -> { + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_LANDSCAPE, + /* expected */ SCREEN_ORIENTATION_REVERSE_LANDSCAPE); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_IsUnchanged() { + runTestScenarioWithActivity((robot) -> { + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_NOSENSOR, + /* expected */ SCREEN_ORIENTATION_NOSENSOR); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() { + runTestScenarioWithActivity((robot) -> { + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_NOSENSOR, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() { + runTestScenarioWithActivity((robot) -> { + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_NOSENSOR); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_propertyIsFalse_isUnchanged() + throws Exception { + runTestScenario((robot) -> { + robot.disableProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); + + robot.createActivityWithComponent(); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, + OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA}) + public void testOverrideOrientationIfNeeded_whenCameraNotActive_isUnchanged() { + runTestScenario((robot) -> { + robot.configureIsCameraCompatTreatmentEnabled(true); + robot.configureIsCameraCompatTreatmentEnabledAtBuildTime(true); + + robot.createActivityWithComponentInNewTask(); + robot.prepareIsTopActivityEligibleForOrientationOverride(false); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, + OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA}) + public void testOverrideOrientationIfNeeded_whenCameraActive_returnsPortrait() { + runTestScenario((robot) -> { + robot.configureIsCameraCompatTreatmentEnabled(true); + robot.configureIsCameraCompatTreatmentEnabledAtBuildTime(true); + + robot.createActivityWithComponentInNewTask(); + robot.prepareIsTopActivityEligibleForOrientationOverride(true); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() { + runTestScenarioWithActivity((robot) -> { + robot.prepareShouldApplyUserFullscreenOverride(true); + robot.configureSetIgnoreOrientationRequest(true); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_USER); + }); + } + + @Test + public void testOverrideOrientationIfNeeded_fullscreenOverride_cameraActivity_unchanged() { + runTestScenario((robot) -> { + robot.configureIsCameraCompatTreatmentEnabled(true); + robot.configureIsCameraCompatTreatmentEnabledAtBuildTime(true); + + robot.createActivityWithComponentInNewTask(); + robot.configureIsTopActivityCameraActive(false); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + public void testOverrideOrientationIfNeeded_respectOrientationRequestOverUserFullScreen() { + runTestScenarioWithActivity((robot) -> { + robot.prepareShouldApplyUserFullscreenOverride(true); + robot.configureSetIgnoreOrientationRequest(false); + + robot.checkOverrideOrientationIsNot(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* notExpected */ SCREEN_ORIENTATION_USER); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() { + runTestScenarioWithActivity((robot) -> { + robot.prepareShouldApplyUserFullscreenOverride(true); + robot.configureSetIgnoreOrientationRequest(true); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_USER); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_respectOrientationReqOverUserFullScreenAndSystem() { + runTestScenarioWithActivity((robot) -> { + robot.prepareShouldApplyUserFullscreenOverride(true); + robot.configureSetIgnoreOrientationRequest(false); + + robot.checkOverrideOrientationIsNot(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* notExpected */ SCREEN_ORIENTATION_USER); + }); + } + + @Test + public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() { + runTestScenarioWithActivity((robot) -> { + robot.prepareShouldApplyUserFullscreenOverride(false); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + }); + } + + @Test + public void testOverrideOrientationIfNeeded_userAspectRatioApplied_unspecifiedOverridden() { + runTestScenarioWithActivity((robot) -> { + robot.prepareShouldApplyUserMinAspectRatioOverride(true); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_LOCKED, + /* expected */ SCREEN_ORIENTATION_PORTRAIT); + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_LANDSCAPE, + /* expected */ SCREEN_ORIENTATION_LANDSCAPE); + }); + } + + @Test + public void testOverrideOrientationIfNeeded_userAspectRatioNotApplied_isUnchanged() { + runTestScenarioWithActivity((robot) -> { + robot.prepareShouldApplyUserFullscreenOverride(false); + + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, + /* expected */ SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + + /** + * Runs a test scenario with an existing activity providing a Robot. + */ + void runTestScenarioWithActivity(@NonNull Consumer<OrientationPolicyRobotTest> consumer) { + runTestScenario(/* withActivity */ true, consumer); + } + + /** + * Runs a test scenario without an existing activity providing a Robot. + */ + void runTestScenario(@NonNull Consumer<OrientationPolicyRobotTest> consumer) { + runTestScenario(/* withActivity */ false, consumer); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(boolean withActivity, + @NonNull Consumer<OrientationPolicyRobotTest> consumer) { + spyOn(mWm.mLetterboxConfiguration); + final OrientationPolicyRobotTest robot = + new OrientationPolicyRobotTest(mWm, mAtm, mSupervisor, withActivity); + consumer.accept(robot); + } + + private static class OrientationPolicyRobotTest { + + @NonNull + private final ActivityTaskManagerService mAtm; + @NonNull + private final WindowManagerService mWm; + @NonNull + private final LetterboxConfiguration mLetterboxConfiguration; + @NonNull + private final TestComponentStack<ActivityRecord> mActivityStack; + @NonNull + private final TestComponentStack<Task> mTaskStack; + + @NonNull + private final ActivityTaskSupervisor mSupervisor; + + OrientationPolicyRobotTest(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor, + boolean withActivity) { + mAtm = atm; + mWm = wm; + spyOn(mWm); + mSupervisor = supervisor; + mActivityStack = new TestComponentStack<>(); + mTaskStack = new TestComponentStack<>(); + mLetterboxConfiguration = mWm.mLetterboxConfiguration; + if (withActivity) { + createActivityWithComponent(); + } + } + + void configureSetIgnoreOrientationRequest(boolean enabled) { + mActivityStack.top().mDisplayContent.setIgnoreOrientationRequest(enabled); + } + + void configureIsUserAppAspectRatioFullscreenEnabled(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled(); + } + + void configureIsCameraCompatTreatmentEnabled(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); + } + + void configureIsCameraCompatTreatmentEnabledAtBuildTime(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabledAtBuildTime(); + } + + void prepareGetUserMinAspectRatioOverrideCode(int orientation) { + spyOn(mActivityStack.top().mLetterboxUiController); + doReturn(orientation).when(mActivityStack.top() + .mLetterboxUiController).getUserMinAspectRatioOverrideCode(); + } + + void prepareShouldApplyUserFullscreenOverride(boolean enabled) { + spyOn(mActivityStack.top().mLetterboxUiController); + doReturn(enabled).when(mActivityStack.top() + .mLetterboxUiController).shouldApplyUserFullscreenOverride(); + } + + void prepareShouldApplyUserMinAspectRatioOverride(boolean enabled) { + spyOn(mActivityStack.top().mLetterboxUiController); + doReturn(enabled).when(mActivityStack.top() + .mLetterboxUiController).shouldApplyUserMinAspectRatioOverride(); + } + + void prepareIsUserAppAspectRatioSettingsEnabled(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled(); + } + + void prepareIsTopActivityEligibleForOrientationOverride(boolean enabled) { + final DisplayRotationCompatPolicy displayPolicy = + mActivityStack.top().mDisplayContent.mDisplayRotationCompatPolicy; + spyOn(displayPolicy); + doReturn(enabled).when(displayPolicy) + .isActivityEligibleForOrientationOverride(eq(mActivityStack.top())); + } + + void configureIsTopActivityCameraActive(boolean enabled) { + final DisplayRotationCompatPolicy displayPolicy = + mActivityStack.top().mDisplayContent.mDisplayRotationCompatPolicy; + spyOn(displayPolicy); + doReturn(enabled).when(displayPolicy) + .isCameraActive(eq(mActivityStack.top()), /* mustBeFullscreen= */ eq(true)); + } + + void disableProperty(@NonNull String propertyName) { + setPropertyValue(propertyName, /* enabled */ false); + } + + int overrideOrientationIfNeeded(@ActivityInfo.ScreenOrientation int candidate) { + return mActivityStack.top().mAppCompatController.getOrientationPolicy() + .overrideOrientationIfNeeded(candidate); + } + + void checkOrientationRequestMapped() { + verify(mWm).mapOrientationRequest(SCREEN_ORIENTATION_PORTRAIT); + } + + void checkOverrideOrientation(@ActivityInfo.ScreenOrientation int candidate, + @ActivityInfo.ScreenOrientation int expected) { + Assert.assertEquals(expected, overrideOrientationIfNeeded(candidate)); + } + + void checkOverrideOrientationIsNot(@ActivityInfo.ScreenOrientation int candidate, + @ActivityInfo.ScreenOrientation int notExpected) { + Assert.assertNotEquals(notExpected, overrideOrientationIfNeeded(candidate)); + } + + private void createActivityWithComponent() { + if (mTaskStack.isEmpty()) { + final DisplayContent displayContent = new TestDisplayContent + .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(displayContent).build(); + mTaskStack.push(task); + } + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setOnTop(true) + .setTask(mTaskStack.top()) + // Set the component to be that of the test class in order + // to enable compat changes + .setComponent(ComponentName.createRelative(mAtm.mContext, + com.android.server.wm.LetterboxUiControllerTest.class.getName())) + .build(); + mActivityStack.push(activity); + } + + private void createActivityWithComponentInNewTask() { + final DisplayContent displayContent = new TestDisplayContent + .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(displayContent).build(); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setOnTop(true) + .setTask(task) + // Set the component to be that of the test class in order + // to enable compat changes + .setComponent(ComponentName.createRelative(mAtm.mContext, + com.android.server.wm.LetterboxUiControllerTest.class.getName())) + .build(); + mTaskStack.push(task); + mActivityStack.push(activity); + } + + private void setPropertyValue(@NonNull String propertyName, boolean enabled) { + PackageManager.Property property = new PackageManager.Property(propertyName, + /* value */ enabled, /* packageName */ "", + /* className */ ""); + PackageManager pm = mWm.mContext.getPackageManager(); + spyOn(pm); + try { + doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); + } catch (PackageManager.NameNotFoundException e) { + fail(e.getLocalizedMessage()); + } + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index a39a1a8637df..8b4d7796290c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -30,6 +30,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityRecord.State.STOPPED; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -245,6 +246,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); // reset drawing status + testCase.recordBack.setState(STOPPED, "stopped"); backNavigationInfo.onBackNavigationFinished(false); mBackNavigationController.clearBackAnimations(); makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow()); @@ -550,7 +552,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { }).when(appWindow.mSession).setOnBackInvokedCallbackInfo(eq(appWindow.mClient), any()); addToWindowMap(appWindow, true); - dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null); + dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null, null); OnBackInvokedCallback appCallback = createBackCallback(appLatch); @@ -937,6 +939,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { testCase.recordFront = record2; testCase.windowBack = window1; testCase.windowFront = window2; + record1.setState(STOPPED, "stopped"); return testCase; } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java index d2494ff3fc9a..fbc4c7b6bd34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java @@ -172,14 +172,14 @@ public class LetterboxTest { @Test public void testSurfaceOrigin_applied() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setPosition(mSurfaces.top, -1000, -2000); } @Test public void testApplySurfaceChanges_setColor() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 0, 0}); @@ -187,7 +187,7 @@ public class LetterboxTest { assertTrue(mLetterbox.needsApplySurfaceChanges()); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 1, 0}); } @@ -195,7 +195,7 @@ public class LetterboxTest { @Test public void testNeedsApplySurfaceChanges_wallpaperBackgroundRequested() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setAlpha(mSurfaces.top, 1.0f); assertFalse(mLetterbox.needsApplySurfaceChanges()); @@ -204,14 +204,14 @@ public class LetterboxTest { assertTrue(mLetterbox.needsApplySurfaceChanges()); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, mDarkScrimAlpha); } @Test public void testNeedsApplySurfaceChanges_setParentSurface() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).reparent(mSurfaces.top, mParentSurface); assertFalse(mLetterbox.needsApplySurfaceChanges()); @@ -220,14 +220,14 @@ public class LetterboxTest { assertTrue(mLetterbox.needsApplySurfaceChanges()); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).reparent(mSurfaces.top, mParentSurface); } @Test public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); assertNull(mSurfaces.fullWindowSurface); } @@ -236,7 +236,7 @@ public class LetterboxTest { public void testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated() { mAreCornersRounded = true; mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); assertNotNull(mSurfaces.fullWindowSurface); } @@ -245,7 +245,7 @@ public class LetterboxTest { public void testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated() { mHasWallpaperBackground = true; mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); assertNotNull(mSurfaces.fullWindowSurface); } @@ -254,7 +254,7 @@ public class LetterboxTest { public void testNotIntersectsOrFullyContains_cornersRounded() { mAreCornersRounded = true; mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); assertTrue(mLetterbox.notIntersectsOrFullyContains(new Rect(1, 2, 9, 9))); } @@ -262,14 +262,19 @@ public class LetterboxTest { @Test public void testSurfaceOrigin_changeCausesReapply() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); clearInvocations(mTransaction); mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0)); assertTrue(mLetterbox.needsApplySurfaceChanges()); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setPosition(mSurfaces.top, 0, 0); } + private void applySurfaceChanges() { + mLetterbox.applySurfaceChanges(/* syncTransaction */ mTransaction, + /* pendingTransaction */ mTransaction); + } + class SurfaceControlMocker implements Supplier<SurfaceControl.Builder> { private SurfaceControl.Builder mLeftBuilder; public SurfaceControl left; diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index cd19449b2037..bdd45c6f9b1e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -18,29 +18,14 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; -import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; -import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; -import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA; -import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; -import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; -import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -50,21 +35,16 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTA import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; -import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.server.wm.LetterboxUiController.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; -import static com.android.server.wm.LetterboxUiController.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS; import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; import static org.junit.Assert.assertEquals; @@ -149,175 +129,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mController = new LetterboxUiController(mWm, mActivity); } - // shouldIgnoreRequestedOrientation - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) - public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() { - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - - assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) - public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() { - doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); - doReturn(true).when(mLetterboxConfiguration) - .isCameraCompatTreatmentEnabledAtBuildTime(); - - // Recreate DisplayContent with DisplayRotationCompatPolicy - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - mController.setRelaunchingAfterRequestedOrientationChanged(false); - - spyOn(mDisplayContent.mDisplayRotationCompatPolicy); - doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy) - .isTreatmentEnabledForActivity(eq(mActivity)); - - assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() { - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - - assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue() - throws Exception { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ true); - mController = new LetterboxUiController(mWm, mActivity); - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - - assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) - public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse() - throws Exception { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ false); - - mController = new LetterboxUiController(mWm, mActivity); - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - - assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - public void testShouldIgnoreOrientationRequestLoop_overrideDisabled_returnsFalse() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - // Request 3 times to simulate orientation request loop - for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ 0); - } - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_propertyIsFalseAndOverride_returnsFalse() - throws Exception { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED, - /* value */ false); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - mController = new LetterboxUiController(mWm, mActivity); - - // Request 3 times to simulate orientation request loop - for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ 0); - } - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_isLetterboxed_returnsFalse() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - // Request 3 times to simulate orientation request loop - for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ i); - } - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_noLoop_returnsFalse() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - // No orientation request loop - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ 0); - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_timeout_returnsFalse() - throws InterruptedException { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - for (int i = MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i > 0; i--) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ 0); - Thread.sleep(SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS); - } - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_returnsTrue() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - for (int i = 0; i < MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ i); - } - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ true, - /* expectedCount */ MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP); - } - - private void assertShouldIgnoreOrientationRequestLoop(boolean shouldIgnore, int expectedCount) { - if (shouldIgnore) { - assertTrue(mController.shouldIgnoreOrientationRequestLoop()); - } else { - assertFalse(mController.shouldIgnoreOrientationRequestLoop()); - } - assertEquals(expectedCount, mController.getSetOrientationRequestCounter()); - } - - @Test - @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) - public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() { - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - doReturn(false).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - - assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - // shouldRefreshActivityForCameraCompat @Test @@ -522,8 +333,9 @@ public class LetterboxUiControllerTest extends WindowTestsBase { final Rect opaqueBounds = new Rect(0, 0, 500, 300); doReturn(opaqueBounds).when(mActivity).getBounds(); // Activity is translucent - spyOn(mActivity.mTransparentPolicy); - when(mActivity.mTransparentPolicy.isRunning()).thenReturn(true); + spyOn(mActivity.mAppCompatController.getTransparentPolicy()); + when(mActivity.mAppCompatController.getTransparentPolicy() + .isRunning()).thenReturn(true); // Makes requested sizes different mainWindow.mRequestedWidth = opaqueBounds.width() - 1; @@ -709,311 +521,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { return mainWindow; } - // overrideOrientationIfNeeded - - @Test - public void testOverrideOrientationIfNeeded_mapInvokedOnRequest() throws Exception { - mController = new LetterboxUiController(mWm, mActivity); - spyOn(mWm); - - mController.overrideOrientationIfNeeded(SCREEN_ORIENTATION_PORTRAIT); - - verify(mWm).mapOrientationRequest(SCREEN_ORIENTATION_PORTRAIT); - } - - @Test - @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) - public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUser() - throws Exception { - mDisplayContent.setIgnoreOrientationRequest(true); - assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) - public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_optOut_returnsUnchanged() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false); - - mController = new LetterboxUiController(mWm, mActivity); - mDisplayContent.setIgnoreOrientationRequest(true); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) - public void testOverrideOrientationIfNeeded_fullscreenOverrides_optOutSystem_returnsUser() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false); - prepareActivityThatShouldApplyUserFullscreenOverride(); - - // fullscreen override still applied - assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) - public void testOverrideOrientationIfNeeded_fullscreenOverrides_optOutUser_returnsUser() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, - /* value */ false); - prepareActivityThatShouldApplyUserFullscreenOverride(); - - // fullscreen override still applied - assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) - public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUnchanged() - throws Exception { - mDisplayContent.setIgnoreOrientationRequest(false); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER}) - public void testOverrideOrientationIfNeeded_fullscreenAndUserOverrideEnabled_returnsUnchanged() - throws Exception { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) - public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait() - throws Exception { - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) - public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() { - assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) - public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_returnsUnchanged() { - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) - public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationPortraitOrUndefined_returnsUnchanged() { - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) - public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationLandscape_returnsReverseLandscape() { - assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_LANDSCAPE)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) - public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_returnsUnchanged() { - assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_NOSENSOR)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) - public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() { - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_NOSENSOR)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION}) - public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() { - assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) - public void testOverrideOrientationIfNeeded_propertyIsFalse_returnsUnchanged() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false); - - mController = new LetterboxUiController(mWm, mActivity); - - assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, - OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA}) - public void testOverrideOrientationIfNeeded_whenCameraNotActive_returnsUnchanged() { - doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); - doReturn(true).when(mLetterboxConfiguration) - .isCameraCompatTreatmentEnabledAtBuildTime(); - - // Recreate DisplayContent with DisplayRotationCompatPolicy - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - - spyOn(mDisplayContent.mDisplayRotationCompatPolicy); - doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy) - .isActivityEligibleForOrientationOverride(eq(mActivity)); - - assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, - OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA}) - public void testOverrideOrientationIfNeeded_whenCameraActive_returnsPortrait() { - doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); - doReturn(true).when(mLetterboxConfiguration) - .isCameraCompatTreatmentEnabledAtBuildTime(); - - // Recreate DisplayContent with DisplayRotationCompatPolicy - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - - spyOn(mDisplayContent.mDisplayRotationCompatPolicy); - doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy) - .isActivityEligibleForOrientationOverride(eq(mActivity)); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() { - spyOn(mController); - doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); - mDisplayContent.setIgnoreOrientationRequest(true); - - assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - public void testOverrideOrientationIfNeeded_userFullscreenOverride_cameraActivity_noChange() { - doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); - doReturn(true).when(mLetterboxConfiguration) - .isCameraCompatTreatmentEnabledAtBuildTime(); - - // Recreate DisplayContent with DisplayRotationCompatPolicy - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - spyOn(mController); - doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); - - spyOn(mDisplayContent.mDisplayRotationCompatPolicy); - doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy) - .isCameraActive(mActivity, /* mustBeFullscreen= */ true); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - public void testOverrideOrientationIfNeeded_systemFullscreenOverride_cameraActivity_noChange() { - doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); - doReturn(true).when(mLetterboxConfiguration) - .isCameraCompatTreatmentEnabledAtBuildTime(); - - // Recreate DisplayContent with DisplayRotationCompatPolicy - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - spyOn(mController); - doReturn(true).when(mController).isSystemOverrideToFullscreenEnabled(); - - spyOn(mDisplayContent.mDisplayRotationCompatPolicy); - doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy) - .isCameraActive(mActivity, /* mustBeFullscreen= */ true); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - public void testOverrideOrientationIfNeeded_respectOrientationRequestOverUserFullScreen() { - spyOn(mController); - doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); - mDisplayContent.setIgnoreOrientationRequest(false); - - assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) - public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() { - spyOn(mController); - doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); - mDisplayContent.setIgnoreOrientationRequest(true); - - assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) - public void testOverrideOrientationIfNeeded_respectOrientationReqOverUserFullScreenAndSystem() { - spyOn(mController); - doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); - mDisplayContent.setIgnoreOrientationRequest(false); - - assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() { - spyOn(mController); - doReturn(false).when(mController).shouldApplyUserFullscreenOverride(); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); - } - - @Test - public void testOverrideOrientationIfNeeded_userAspectRatioApplied_unspecifiedOverridden() { - spyOn(mController); - doReturn(true).when(mController).shouldApplyUserMinAspectRatioOverride(); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_LOCKED)); - - // unchanged if orientation is specified - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_LANDSCAPE)); - } - - @Test - public void testOverrideOrientationIfNeeded_userAspectRatioNotApplied_returnsUnchanged() { - spyOn(mController); - doReturn(false).when(mController).shouldApplyUserMinAspectRatioOverride(); - - assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded( - /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); - } - // shouldApplyUser...Override @Test public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception { @@ -1062,6 +569,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { prepareActivityThatShouldApplyUserMinAspectRatioOverride(); mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); + mActivity = setUpActivityWithComponent(); mController = new LetterboxUiController(mWm, mActivity); assertFalse(mController.shouldEnableUserAspectRatioSettings()); @@ -1344,6 +852,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse() throws Exception { mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); + + mActivity = setUpActivityWithComponent(); mController = new LetterboxUiController(mWm, mActivity); assertFalse(mController.shouldOverrideMinAspectRatio()); @@ -1472,6 +982,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testshouldOverrideForceResizeApp_propertyFalse_overrideEnabled_returnsFalse() throws Exception { mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false); + + mActivity = setUpActivityWithComponent(); mController = new LetterboxUiController(mWm, mActivity); assertFalse(mController.shouldOverrideForceResizeApp()); @@ -1528,6 +1040,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testshouldOverrideForceNonResizeApp_propertyFalse_overrideEnabled_returnsFalse() throws Exception { mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false); + + mActivity = setUpActivityWithComponent(); mController = new LetterboxUiController(mWm, mActivity); assertFalse(mController.shouldOverrideForceNonResizeApp()); @@ -1684,12 +1198,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mDisplayContent.setIgnoreOrientationRequest(true); } - private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - mController.setRelaunchingAfterRequestedOrientationChanged(true); - } - private ActivityRecord setUpActivityWithComponent() { mDisplayContent = new TestDisplayContent .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index ac1aa20b322f..3a854511e3de 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4271,6 +4271,27 @@ public class SizeCompatTests extends WindowTestsBase { } + @Test + public void testInsetOverrideNotAppliedInFreeform() { + final int notchHeight = 100; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) + .setNotch(notchHeight) + .build(); + setUpApp(display); + + // Simulate inset override for legacy app bound behaviour + mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true; + // Set task as freeform + mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + Rect bounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds()); + // App bounds should not include insets and should match bounds when in freeform. + assertEquals(new Rect(0, 0, 1000, 2800), appBounds); + assertEquals(new Rect(0, 0, 1000, 2800), bounds); + } + private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity( float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox, Rect sizeCompatUnscaled, Rect sizeCompatScaled) { diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java index 6c5f9752b6fc..1c32980aac91 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java @@ -33,6 +33,7 @@ import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -417,6 +418,22 @@ public class SyncEngineTests extends WindowTestsBase { } @Test + public void testSkipPrepareSync() { + final TestWindowContainer wc = new TestWindowContainer(mWm, true /* waiter */); + wc.mSkipPrepareSync = true; + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); + final BLASTSyncEngine.SyncGroup syncGroup = bse.prepareSyncSet( + mock(BLASTSyncEngine.TransactionReadyListener.class), "test"); + bse.startSyncSet(syncGroup); + bse.addToSyncSet(syncGroup.mSyncId, wc); + assertEquals(SYNC_STATE_NONE, wc.mSyncState); + // If the implementation of prepareSync doesn't set sync state, the sync group should also + // be empty. + assertNull(wc.mSyncGroup); + assertTrue(wc.isSyncFinished(syncGroup)); + } + + @Test public void testNonBlastMethod() { mAppWindow = createWindow(null, TYPE_BASE_APPLICATION, "mAppWindow"); @@ -694,6 +711,7 @@ public class SyncEngineTests extends WindowTestsBase { final boolean mWaiter; boolean mVisibleRequested = true; boolean mFillsParent = false; + boolean mSkipPrepareSync = false; TestWindowContainer(WindowManagerService wms, boolean waiter) { super(wms); @@ -703,6 +721,9 @@ public class SyncEngineTests extends WindowTestsBase { @Override boolean prepareSync() { + if (mSkipPrepareSync) { + return false; + } if (!super.prepareSync()) { return false; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index f94e5e3c38ed..35b6b70cb611 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -499,8 +499,8 @@ public class TaskFragmentTest extends WindowTestsBase { .build(); final ActivityRecord activity0 = organizedTf.getBottomMostActivity(); final ActivityRecord activity1 = organizedTf.getTopMostActivity(); - // Bottom activity is untrusted embedding. Top activity is trusted embedded. - // Activity0 has overlay over untrusted mode embedded. + // Bottom activity is untrusted embedding. Top activity is trusted embedded and occludes + // the bottom activity. Activity0 has overlay over untrusted mode embedded. activity0.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID + 1; activity1.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID; doReturn(true).when(organizedTf).isAllowedToEmbedActivityInUntrustedMode(activity0); @@ -537,6 +537,48 @@ public class TaskFragmentTest extends WindowTestsBase { } @Test + public void testActivityHasOverlayOverUntrustedModeEmbeddedWithAdjacentTaskFragments() { + final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, + ACTIVITY_TYPE_STANDARD); + final Rect taskBounds = rootTask.getBounds(); + final TaskFragment organizedTf0 = new TaskFragmentBuilder(mAtm) + .createActivityCount(1) + .setParentTask(rootTask) + .setFragmentToken(new Binder()) + .setOrganizer(mOrganizer) + .setBounds(new Rect(taskBounds.left, taskBounds.top, + taskBounds.left + taskBounds.width() / 2, taskBounds.bottom)) + .build(); + final TaskFragment organizedTf1 = new TaskFragmentBuilder(mAtm) + .createActivityCount(1) + .setParentTask(rootTask) + .setFragmentToken(new Binder()) + .setOrganizer(mOrganizer) + .setBounds(new Rect(taskBounds.left + taskBounds.width() / 2, taskBounds.top, + taskBounds.right, taskBounds.bottom)) + .build(); + final ActivityRecord activity0 = organizedTf0.getTopMostActivity(); + final ActivityRecord activity1 = organizedTf1.getTopMostActivity(); + + activity0.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID + 1; + activity1.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID; + doReturn(true).when(organizedTf0).isAllowedToEmbedActivityInUntrustedMode(activity0); + + assertFalse("Activity0 doesn't have overlay because it's not occluded by activity1", + activity0.hasOverlayOverUntrustedModeEmbedded()); + assertFalse(activity1.hasOverlayOverUntrustedModeEmbedded()); + + // Expand organizedTf1 bounds slightly. + final Rect tfBounds1 = organizedTf1.getBounds(); + organizedTf1.setBounds(tfBounds1.left - 5, tfBounds1.top, taskBounds.right + 5, + tfBounds1.bottom); + + assertTrue("Activity0 has overlay because it's occluded partially by activity1", + activity0.hasOverlayOverUntrustedModeEmbedded()); + assertFalse(activity1.hasOverlayOverUntrustedModeEmbedded()); + } + + @Test public void testIsAllowedToBeEmbeddedInTrustedMode() { final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) .setCreateParentTask() diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java index 1d6e30777922..628c65e992fd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java @@ -46,12 +46,14 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import androidx.annotation.NonNull; + +import com.android.server.wm.utils.TestComponentStack; + import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -318,21 +320,23 @@ public class TransparentPolicyTest extends WindowTestsBase { */ private static class TransparentPolicyRobotTest { + @NonNull private final ActivityTaskManagerService mAtm; - + @NonNull private final Task mTask; - - private final ActivityStackTest mActivityStack; - + @NonNull + private final TestComponentStack<ActivityRecord> mActivityStack; + @NonNull private WindowConfiguration mTopActivityWindowConfiguration; - private TransparentPolicyRobotTest(ActivityTaskManagerService atm, Task task, - ActivityRecord opaqueActivity) { + private TransparentPolicyRobotTest(@NonNull ActivityTaskManagerService atm, + @NonNull Task task, + @NonNull ActivityRecord opaqueActivity) { mAtm = atm; mTask = task; - mActivityStack = new ActivityStackTest(); - mActivityStack.pushActivity(opaqueActivity); - spyOn(opaqueActivity.mTransparentPolicy); + mActivityStack = new TestComponentStack<>(); + mActivityStack.push(opaqueActivity); + spyOn(opaqueActivity.mAppCompatController.getTransparentPolicy()); } void configureTopActivityAsEmbedded() { @@ -360,8 +364,8 @@ public class TransparentPolicyTest extends WindowTestsBase { if (addToTask) { mTask.addChild(newActivity); } - spyOn(newActivity.mTransparentPolicy); - mActivityStack.pushActivity(newActivity); + spyOn(newActivity.mAppCompatController.getTransparentPolicy()); + mActivityStack.push(newActivity); } void attachTopActivityToTask() { @@ -406,31 +410,34 @@ public class TransparentPolicyTest extends WindowTestsBase { } void checkTopActivityPolicyStateIsRunning() { - assertTrue(mActivityStack.top().mTransparentPolicy.isRunning()); + assertTrue(mActivityStack.top().mAppCompatController + .getTransparentPolicy().isRunning()); } void checkTopActivityPolicyStateIsNotRunning() { - assertFalse(mActivityStack.top().mTransparentPolicy.isRunning()); + assertFalse(mActivityStack.top().mAppCompatController + .getTransparentPolicy().isRunning()); } void checkTopActivityPolicyStopInvoked() { - verify(mActivityStack.top().mTransparentPolicy).stop(); + verify(mActivityStack.top().mAppCompatController.getTransparentPolicy()).stop(); } void checkTopActivityPolicyStopNotInvoked() { mActivityStack.applyToTop((activity) -> { - verify(activity.mTransparentPolicy, never()).stop(); + verify(activity.mAppCompatController.getTransparentPolicy(), never()).stop(); }); } void checkTopActivityPolicyStartInvoked() { mActivityStack.applyToTop((activity) -> { - verify(activity.mTransparentPolicy).start(); + verify(activity.mAppCompatController.getTransparentPolicy()).start(); }); } void checkTopActivityPolicyStartNotInvoked() { - verify(mActivityStack.top().mTransparentPolicy, never()).start(); + verify(mActivityStack.top().mAppCompatController.getTransparentPolicy(), + never()).start(); } void assertTrueOnActivity(int fromTop, Predicate<ActivityRecord> predicate) { @@ -505,7 +512,7 @@ public class TransparentPolicyTest extends WindowTestsBase { void clearInteractions() { mActivityStack.applyToAll((activity) -> { clearInvocations(activity); - clearInvocations(activity.mTransparentPolicy); + clearInvocations(activity.mAppCompatController.getTransparentPolicy()); }); } @@ -593,45 +600,5 @@ public class TransparentPolicyTest extends WindowTestsBase { display.computeScreenConfiguration(c); display.onRequestedOverrideConfigurationChanged(c); } - - /** - * Contains all the ActivityRecord launched in the test. This is different from what's in - * the Task because activities are added here even if not added to tasks. - */ - private static class ActivityStackTest { - private final List<ActivityRecord> mActivities = new ArrayList<>(); - - void pushActivity(ActivityRecord activityRecord) { - mActivities.add(activityRecord); - } - - void applyToTop(Consumer<ActivityRecord> consumer) { - consumer.accept(top()); - } - - ActivityRecord getFromTop(int fromTop) { - return mActivities.get(mActivities.size() - fromTop - 1); - } - - ActivityRecord base() { - return mActivities.get(0); - } - - ActivityRecord top() { - return mActivities.get(mActivities.size() - 1); - } - - // Allows access to the activity at position beforeLast from the top. - // If fromTop = 0 the activity used is the top one. - void applyTo(int fromTop, Consumer<ActivityRecord> consumer) { - consumer.accept(getFromTop(fromTop)); - } - - void applyToAll(Consumer<ActivityRecord> consumer) { - for (int i = mActivities.size() - 1; i >= 0; i--) { - consumer.accept(mActivities.get(i)); - } - } - } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 4ebbf496b2a4..a94b58690775 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -163,7 +163,7 @@ public class WindowContainerTests extends WindowTestsBase { @Test public void testAddChildSetsSurfacePosition() { reset(mTransaction); - try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mWm)) { + try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mDisplayContent)) { WindowContainer child = new WindowContainer(mWm); child.setBounds(1, 1, 10, 10); @@ -266,7 +266,7 @@ public class WindowContainerTests extends WindowTestsBase { @Test public void testRemoveImmediatelyClearsLastSurfacePosition() { reset(mTransaction); - try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mWm)) { + try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mDisplayContent)) { final WindowContainer<WindowContainer> child1 = new WindowContainer(mWm); child1.setBounds(1, 1, 10, 10); @@ -1827,8 +1827,9 @@ public class WindowContainerTests extends WindowTestsBase { implements AutoCloseable { private final SurfaceSession mSession = new SurfaceSession(); - MockSurfaceBuildingContainer(WindowManagerService wm) { - super(wm); + MockSurfaceBuildingContainer(DisplayContent dc) { + super(dc.mWmService); + onDisplayChanged(dc); } static class MockSurfaceBuilder extends SurfaceControl.Builder { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 24ebad60a191..fcf7a3fe79c1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -78,7 +78,6 @@ import android.app.IApplicationThread; import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.os.Binder; -import android.os.Bundle; import android.os.IBinder; import android.os.InputConfig; import android.os.Process; @@ -94,7 +93,6 @@ import android.util.ArraySet; import android.util.MergedConfiguration; import android.view.ContentRecordingSession; import android.view.IWindow; -import android.view.IWindowSession; import android.view.InputChannel; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -265,69 +263,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void testRelayoutExitingWindow_legacy() { - mSetFlagsRule.disableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO); - - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin"); - final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class); - win.mWinAnimator.mSurfaceController = surfaceController; - win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; - doReturn(true).when(surfaceController).hasSurface(); - spyOn(win.mTransitionController); - doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled(); - doReturn(true).when(win.mTransitionController).inTransition( - eq(win.mActivityRecord)); - win.mViewVisibility = View.VISIBLE; - win.mHasSurface = true; - win.mActivityRecord.mAppStopped = true; - mWm.mWindowMap.put(win.mClient.asBinder(), win); - spyOn(mWm.mWindowPlacerLocked); - // Skip unnecessary operations of relayout. - doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean()); - final int w = 100; - final int h = 200; - final ClientWindowFrames outFrames = new ClientWindowFrames(); - final MergedConfiguration outConfig = new MergedConfiguration(); - final SurfaceControl outSurfaceControl = new SurfaceControl(); - final InsetsState outInsetsState = new InsetsState(); - final InsetsSourceControl.Array outControls = new InsetsSourceControl.Array(); - final Bundle outBundle = new Bundle(); - mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0, - outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); - // The window is in transition, so its destruction is deferred. - assertTrue(win.mAnimatingExit); - assertFalse(win.mDestroying); - assertTrue(win.mTransitionController.mAnimatingExitWindows.contains(win)); - - win.mAnimatingExit = false; - win.mViewVisibility = View.VISIBLE; - win.mActivityRecord.setVisibleRequested(false); - win.mActivityRecord.setVisible(false); - mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0, - outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); - // Because the window is already invisible, it doesn't need to apply exiting animation - // and WMS#tryStartExitingAnimation() will destroy the surface directly. - assertFalse(win.mAnimatingExit); - assertFalse(win.mHasSurface); - assertNull(win.mWinAnimator.mSurfaceController); - - // Invisible requested activity should not get the last config even if its view is visible. - mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0, - outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); - assertEquals(0, outConfig.getMergedConfiguration().densityDpi); - // Non activity window can still get the last config. - win.mActivityRecord = null; - win.fillClientWindowFramesAndConfiguration(outFrames, outConfig, - null /* outActivityWindowInfo*/, false /* useLatestConfig */, - true /* relayoutVisible */); - assertEquals(win.getConfiguration().densityDpi, - outConfig.getMergedConfiguration().densityDpi); - } - - @Test public void testRelayoutExitingWindow() { - mSetFlagsRule.enableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin"); final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class); win.mWinAnimator.mSurfaceController = surfaceController; @@ -483,15 +419,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { win.mRelayoutSeq = 1; seq = 2; } - if (Flags.windowSessionRelayoutInfo()) { - mWm.relayoutWindow(win.mSession, win.mClient, newParams, 100, 200, View.VISIBLE, 0, seq, - 0, new WindowRelayoutResult()); - } else { - mWm.relayoutWindow(win.mSession, win.mClient, newParams, 100, 200, View.VISIBLE, 0, seq, - 0, new ClientWindowFrames(), new MergedConfiguration(), - new SurfaceControl(), new InsetsState(), new InsetsSourceControl.Array(), - new Bundle()); - } + mWm.relayoutWindow(win.mSession, win.mClient, newParams, 100, 200, View.VISIBLE, 0, seq, + 0, new WindowRelayoutResult()); ArgumentCaptor<Integer> changedFlags = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<Integer> changedPrivateFlags = ArgumentCaptor.forClass(Integer.class); @@ -1364,70 +1293,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void testRelayout_appWindowSendActivityWindowInfo_legacy() { - mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); - mSetFlagsRule.disableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO); - - // Skip unnecessary operations of relayout. - spyOn(mWm.mWindowPlacerLocked); - doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean()); - - final Task task = createTask(mDisplayContent); - final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "appWindow"); - mWm.mWindowMap.put(win.mClient.asBinder(), win); - - final int w = 100; - final int h = 200; - final ClientWindowFrames outFrames = new ClientWindowFrames(); - final MergedConfiguration outConfig = new MergedConfiguration(); - final SurfaceControl outSurfaceControl = new SurfaceControl(); - final InsetsState outInsetsState = new InsetsState(); - final InsetsSourceControl.Array outControls = new InsetsSourceControl.Array(); - final Bundle outBundle = new Bundle(); - - final ActivityRecord activity = win.mActivityRecord; - final ActivityWindowInfo expectedInfo = new ActivityWindowInfo(); - expectedInfo.set(true, new Rect(0, 0, 1000, 2000), new Rect(0, 0, 500, 2000)); - doReturn(expectedInfo).when(activity).getActivityWindowInfo(); - activity.setVisibleRequested(false); - activity.setVisible(false); - - mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0, - outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); - - // No latest reported value, so return empty when activity is invisible - final ActivityWindowInfo activityWindowInfo = outBundle.getParcelable( - IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, ActivityWindowInfo.class); - assertEquals(new ActivityWindowInfo(), activityWindowInfo); - - activity.setVisibleRequested(true); - activity.setVisible(true); - - mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0, - outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); - - // Report the latest when activity is visible. - final ActivityWindowInfo activityWindowInfo2 = outBundle.getParcelable( - IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, ActivityWindowInfo.class); - assertEquals(expectedInfo, activityWindowInfo2); - - expectedInfo.set(false, new Rect(0, 0, 1000, 2000), new Rect(0, 0, 1000, 2000)); - activity.setVisibleRequested(false); - activity.setVisible(false); - - mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0, - outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); - - // Report the last reported value when activity is invisible. - final ActivityWindowInfo activityWindowInfo3 = outBundle.getParcelable( - IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, ActivityWindowInfo.class); - assertEquals(activityWindowInfo2, activityWindowInfo3); - } - - @Test public void testRelayout_appWindowSendActivityWindowInfo() { mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); - mSetFlagsRule.enableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO); // Skip unnecessary operations of relayout. spyOn(mWm.mWindowPlacerLocked); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index b152c3e5355f..e13376b34e7b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -86,7 +86,6 @@ import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; -import android.os.Bundle; import android.os.IBinder; import android.os.InputConfig; import android.os.RemoteException; @@ -114,7 +113,6 @@ import androidx.test.filters.SmallTest; import com.android.server.testutils.StubTransaction; import com.android.server.wm.SensitiveContentPackages.PackageInfo; -import com.android.window.flags.Flags; import org.junit.After; import org.junit.Test; @@ -1369,67 +1367,7 @@ public class WindowStateTests extends WindowTestsBase { @SetupWindows(addWindows = {W_INPUT_METHOD}) @Test - public void testImeTargetChangeListener_OnImeTargetOverlayVisibilityChanged_legacy() { - mSetFlagsRule.disableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO); - - final TestImeTargetChangeListener listener = new TestImeTargetChangeListener(); - mWm.mImeTargetChangeListener = listener; - - // Scenario 1: test addWindow/relayoutWindow to add Ime layering overlay window as visible. - final WindowToken windowToken = createTestWindowToken(TYPE_APPLICATION_OVERLAY, - mDisplayContent); - final IWindow client = new TestIWindow(); - final Session session = getTestSession(); - final ClientWindowFrames outFrames = new ClientWindowFrames(); - final MergedConfiguration outConfig = new MergedConfiguration(); - final SurfaceControl outSurfaceControl = new SurfaceControl(); - final InsetsState outInsetsState = new InsetsState(); - final InsetsSourceControl.Array outControls = new InsetsSourceControl.Array(); - final Bundle outBundle = new Bundle(); - final WindowManager.LayoutParams params = new WindowManager.LayoutParams( - TYPE_APPLICATION_OVERLAY); - params.setTitle("imeLayeringTargetOverlay"); - params.token = windowToken.token; - params.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM; - - mWm.addWindow(session, client, params, View.VISIBLE, DEFAULT_DISPLAY, - 0 /* userUd */, WindowInsets.Type.defaultVisible(), null, new InsetsState(), - new InsetsSourceControl.Array(), new Rect(), new float[1]); - mWm.relayoutWindow(session, client, params, 100, 200, View.VISIBLE, 0, 0, 0, - outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); - waitHandlerIdle(mWm.mH); - - final WindowState imeLayeringTargetOverlay = mDisplayContent.getWindow( - w -> w.mClient.asBinder() == client.asBinder()); - assertThat(imeLayeringTargetOverlay.isVisible()).isTrue(); - assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder()); - assertThat(listener.mIsRemoved).isFalse(); - assertThat(listener.mIsVisibleForImeTargetOverlay).isTrue(); - - // Scenario 2: test relayoutWindow to let the Ime layering target overlay window invisible. - mWm.relayoutWindow(session, client, params, 100, 200, View.GONE, 0, 0, 0, - outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); - waitHandlerIdle(mWm.mH); - - assertThat(imeLayeringTargetOverlay.isVisible()).isFalse(); - assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder()); - assertThat(listener.mIsRemoved).isFalse(); - assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse(); - - // Scenario 3: test removeWindow to remove the Ime layering target overlay window. - mWm.removeClientToken(session, client.asBinder()); - waitHandlerIdle(mWm.mH); - - assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder()); - assertThat(listener.mIsRemoved).isTrue(); - assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse(); - } - - @SetupWindows(addWindows = {W_INPUT_METHOD}) - @Test public void testImeTargetChangeListener_OnImeTargetOverlayVisibilityChanged() { - mSetFlagsRule.enableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO); - final TestImeTargetChangeListener listener = new TestImeTargetChangeListener(); mWm.mImeTargetChangeListener = listener; diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/TestComponentStack.java b/services/tests/wmtests/src/com/android/server/wm/utils/TestComponentStack.java new file mode 100644 index 000000000000..a06c0a216d9e --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/TestComponentStack.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.utils; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Contains all the component created in the test. + */ +public class TestComponentStack<T> { + + @NonNull + private final List<T> mItems = new ArrayList<>(); + + /** + * Adds an item to the stack. + * + * @param item The item to add. + */ + public void push(@NonNull T item) { + mItems.add(item); + } + + /** + * Consumes the top element of the stack. + * + * @param consumer Consumer for the optional top element. + * @throws IndexOutOfBoundsException In case that stack is empty. + */ + public void applyToTop(@NonNull Consumer<T> consumer) { + consumer.accept(top()); + } + + /** + * Returns the item at fromTop position from the top one if present or it throws an + * exception if not present. + * + * @param fromTop The position from the top of the item to return. + * @return The returned item. + * @throws IndexOutOfBoundsException In case that position doesn't exist. + */ + @NonNull + public T getFromTop(int fromTop) { + return mItems.get(mItems.size() - fromTop - 1); + } + + /** + * @return The item at the base of the stack if present. + * @throws IndexOutOfBoundsException In case that stack is empty. + */ + @NonNull + public T base() { + return mItems.get(0); + } + + /** + * @return The item at the top of the stack if present. + * @throws IndexOutOfBoundsException In case that stack is empty. + */ + @NonNull + public T top() { + return mItems.get(mItems.size() - 1); + } + + /** + * @return {@code true} if the stack is empty. + */ + public boolean isEmpty() { + return mItems.isEmpty(); + } + + /** + * Allows access to the item at position beforeLast from the top. + * + * @param fromTop The position from the top of the item to return. + * @param consumer Consumer for the optional returned element. + */ + public void applyTo(int fromTop, @NonNull Consumer<T> consumer) { + consumer.accept(getFromTop(fromTop)); + } + + /** + * Invoked the consumer iterating over all the elements in the stack. + * + * @param consumer Consumer for the elements. + */ + public void applyToAll(@NonNull Consumer<T> consumer) { + for (int i = mItems.size() - 1; i >= 0; i--) { + consumer.accept(mItems.get(i)); + } + } +} diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 0ecafc70b9a4..019fb7b7affe 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -49,6 +49,7 @@ import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -1285,6 +1286,13 @@ public class PhoneNumberUtils { private static final String SINGAPORE_ISO_COUNTRY_CODE = "SG"; + private static final String[] COUNTRY_CODES_TO_FORMAT_NATIONALLY = new String[] { + "KR", // Korea + "JP", // Japan + "SG", // Singapore + "TW", // Taiwan + }; + /** * Breaks the given number down and formats it according to the rules * for the country the number is from. @@ -1647,45 +1655,58 @@ public class PhoneNumberUtils { defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT); } + Rlog.v(LOG_TAG, "formatNumber: defaultCountryIso: " + defaultCountryIso); + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); String result = null; try { PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); - if (KOREA_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && - (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) && - (pn.getCountryCodeSource() == - PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { - /** - * Need to reformat any local Korean phone numbers (when the user is in Korea) with - * country code to corresponding national format which would replace the leading - * +82 with 0. - */ - result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); - } else if (JAPAN_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && - pn.getCountryCode() == util.getCountryCodeForRegion(JAPAN_ISO_COUNTRY_CODE) && - (pn.getCountryCodeSource() == - PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { - /** - * Need to reformat Japanese phone numbers (when user is in Japan) with the national - * dialing format. - */ - result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); - } else if (Flags.removeCountryCodeFromLocalSingaporeCalls() && - (SINGAPORE_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && - pn.getCountryCode() == - util.getCountryCodeForRegion(SINGAPORE_ISO_COUNTRY_CODE) && - (pn.getCountryCodeSource() == - PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN))) { - /* - * Need to reformat Singaporean phone numbers (when the user is in Singapore) - * with the country code (+65) removed to comply with Singaporean regulations. - */ - result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); + if (Flags.nationalCountryCodeFormattingForLocalCalls()) { + if (Arrays.asList(COUNTRY_CODES_TO_FORMAT_NATIONALLY).contains(defaultCountryIso) + && pn.getCountryCode() == util.getCountryCodeForRegion(defaultCountryIso) + && pn.getCountryCodeSource() + == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN) { + return util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); + } else { + return util.formatInOriginalFormat(pn, defaultCountryIso); + } } else { - result = util.formatInOriginalFormat(pn, defaultCountryIso); + if (KOREA_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && ( + pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) + && (pn.getCountryCodeSource() + == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { + /** + * Need to reformat any local Korean phone numbers (when the user is in + * Korea) with country code to corresponding national format which would + * replace the leading +82 with 0. + */ + result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); + } else if (JAPAN_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) + && pn.getCountryCode() == util.getCountryCodeForRegion( + JAPAN_ISO_COUNTRY_CODE) && (pn.getCountryCodeSource() + == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { + /** + * Need to reformat Japanese phone numbers (when user is in Japan) with the + * national dialing format. + */ + result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); + } else if (Flags.removeCountryCodeFromLocalSingaporeCalls() && ( + SINGAPORE_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) + && pn.getCountryCode() == util.getCountryCodeForRegion( + SINGAPORE_ISO_COUNTRY_CODE) && (pn.getCountryCodeSource() + == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN))) { + /* + * Need to reformat Singaporean phone numbers (when the user is in Singapore) + * with the country code (+65) removed to comply with Singaporean regulations. + */ + result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); + } else { + result = util.formatInOriginalFormat(pn, defaultCountryIso); + } } } catch (NumberParseException e) { + if (DBG) log("formatNumber: NumberParseException caught " + e); } return result; } diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java index eadb726bf63b..2b515c9b5cd1 100644 --- a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java +++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java @@ -64,6 +64,14 @@ public final class VisualVoicemailSmsFilterSettings implements Parcelable { * @hide */ public static final int DEFAULT_DESTINATION_PORT = DESTINATION_PORT_ANY; + /** + * @hide + */ + public static final int MAX_STRING_LENGTH = 256; + /** + * @hide + */ + public static final int MAX_LIST_SIZE = 100; /** * Builder class for {@link VisualVoicemailSmsFilterSettings} objects. @@ -82,11 +90,16 @@ public final class VisualVoicemailSmsFilterSettings implements Parcelable { /** * Sets the client prefix for the visual voicemail SMS filter. The client prefix will appear * at the start of a visual voicemail SMS message, followed by a colon(:). + * @throws IllegalArgumentException if the string length is greater than 256 characters */ public Builder setClientPrefix(String clientPrefix) { if (clientPrefix == null) { throw new IllegalArgumentException("Client prefix cannot be null"); } + if (clientPrefix.length() > MAX_STRING_LENGTH) { + throw new IllegalArgumentException("Client prefix cannot be greater than " + + MAX_STRING_LENGTH + " characters"); + } mClientPrefix = clientPrefix; return this; } @@ -95,11 +108,25 @@ public final class VisualVoicemailSmsFilterSettings implements Parcelable { * Sets the originating number allow list for the visual voicemail SMS filter. If the list * is not null only the SMS messages from a number in the list can be considered as a visual * voicemail SMS. Otherwise, messages from any address will be considered. + * @throws IllegalArgumentException if the size of the originatingNumbers list is greater + * than 100 elements + * @throws IllegalArgumentException if an element within the originatingNumbers list has + * a string length greater than 256 */ public Builder setOriginatingNumbers(List<String> originatingNumbers) { if (originatingNumbers == null) { throw new IllegalArgumentException("Originating numbers cannot be null"); } + if (originatingNumbers.size() > MAX_LIST_SIZE) { + throw new IllegalArgumentException("The originatingNumbers list size cannot be" + + " greater than " + MAX_STRING_LENGTH + " elements"); + } + for (String num : originatingNumbers) { + if (num != null && num.length() > MAX_STRING_LENGTH) { + throw new IllegalArgumentException("Numbers within the originatingNumbers list" + + " cannot be greater than" + MAX_STRING_LENGTH + " characters"); + } + } mOriginatingNumbers = originatingNumbers; return this; } diff --git a/test-mock/Android.bp b/test-mock/Android.bp index e29d321e5105..59766579eee2 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -51,10 +51,8 @@ java_sdk_library { java_library { name: "android.test.mock.ravenwood", + defaults: ["ravenwood-internal-only-visibility-java"], srcs: [":android-test-mock-sources"], - visibility: [ - "//frameworks/base", - ], } android_ravenwood_test { diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/tests/testables/src/android/animation/AnimatorTestRule.java index 3b39e1fc6bc7..3b39e1fc6bc7 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java +++ b/tests/testables/src/android/animation/AnimatorTestRule.java diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp index 06449e0ce574..d6a4754c37aa 100644 --- a/tests/testables/tests/Android.bp +++ b/tests/testables/tests/Android.bp @@ -26,14 +26,18 @@ android_test { platform_apis: true, srcs: [ "src/**/*.java", + "src/**/*.kt", "src/**/I*.aidl", ], resource_dirs: ["res"], static_libs: [ + "androidx.core_core-animation", + "androidx.core_core-ktx", "androidx.test.rules", "hamcrest-library", "mockito-target-inline-minus-junit4", "testables", + "truth", ], compile_multilib: "both", jni_libs: [ diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt index f23fbee18904..5abebee77d3d 100644 --- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt +++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt @@ -19,7 +19,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.core.animation.doOnEnd import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -32,7 +31,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest @RunWithLooper -class AnimatorTestRuleIsolationTest : SysuiTestCase() { +class AnimatorTestRuleIsolationTest { @get:Rule val animatorTestRule = AnimatorTestRule(this) @@ -41,7 +40,7 @@ class AnimatorTestRuleIsolationTest : SysuiTestCase() { // GIVEN global state is reset at the start of the test didTouchA = false didTouchB = false - // WHEN starting 2 animations of different durations, and setting didTouchA at the end + // WHEN starting 2 animations of different durations, and setting didTouch{A,B} at the end ObjectAnimator.ofFloat(0f, 1f).apply { duration = 100 doOnEnd { didTouchA = true } @@ -49,7 +48,7 @@ class AnimatorTestRuleIsolationTest : SysuiTestCase() { } ObjectAnimator.ofFloat(0f, 1f).apply { duration = 150 - doOnEnd { didTouchA = true } + doOnEnd { didTouchB = true } start() } // WHEN when you advance time so that only one of the animations has ended @@ -65,7 +64,7 @@ class AnimatorTestRuleIsolationTest : SysuiTestCase() { // GIVEN global state is reset at the start of the test didTouchA = false didTouchB = false - // WHEN starting 2 animations of different durations, and setting didTouchB at the end + // WHEN starting 2 animations of different durations, and setting didTouch{A,B} at the end ObjectAnimator.ofFloat(0f, 1f).apply { duration = 100 doOnEnd { didTouchB = true } @@ -73,7 +72,7 @@ class AnimatorTestRuleIsolationTest : SysuiTestCase() { } ObjectAnimator.ofFloat(0f, 1f).apply { duration = 150 - doOnEnd { didTouchB = true } + doOnEnd { didTouchA = true } start() } animatorTestRule.advanceTimeBy(100) diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt index fd5f1573ba5e..9eeaad5cd272 100644 --- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt +++ b/tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt @@ -17,10 +17,9 @@ package android.animation import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.view.animation.LinearInterpolator import androidx.core.animation.doOnEnd import androidx.test.filters.SmallTest -import com.android.app.animation.Interpolators -import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -29,7 +28,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest @RunWithLooper -class AnimatorTestRulePrecisionTest : SysuiTestCase() { +class AnimatorTestRulePrecisionTest { @get:Rule val animatorTestRule = AnimatorTestRule(this) @@ -43,7 +42,7 @@ class AnimatorTestRulePrecisionTest : SysuiTestCase() { crossinline onEndAction: (animator: Animator) -> Unit, ) { ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also { - it.interpolator = Interpolators.LINEAR + it.interpolator = LINEAR_INTERPOLATOR it.duration = duration it.startDelay = startDelay it.doOnEnd(onEndAction) @@ -190,4 +189,8 @@ class AnimatorTestRulePrecisionTest : SysuiTestCase() { assertThat(ended2).isTrue() assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0) } + + private companion object { + private val LINEAR_INTERPOLATOR = LinearInterpolator() + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java index 129733ebc1b6..f39722589a96 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java @@ -25,99 +25,42 @@ import java.util.List; /** AppInfo representation */ public class AppInfo implements AslMarshallable { - private final String mTitle; - private final String mDescription; - private final Boolean mContainsAds; - private final Boolean mObeyAps; - private final Boolean mAdsFingerprinting; - private final Boolean mSecurityFingerprinting; + private final Boolean mApsCompliant; private final String mPrivacyPolicy; - private final List<String> mSecurityEndpoints; private final List<String> mFirstPartyEndpoints; private final List<String> mServiceProviderEndpoints; - private final String mCategory; - private final String mEmail; - private final String mWebsite; public AppInfo( - String title, - String description, - Boolean containsAds, - Boolean obeyAps, - Boolean adsFingerprinting, - Boolean securityFingerprinting, + Boolean apsCompliant, String privacyPolicy, - List<String> securityEndpoints, List<String> firstPartyEndpoints, - List<String> serviceProviderEndpoints, - String category, - String email, - String website) { - this.mTitle = title; - this.mDescription = description; - this.mContainsAds = containsAds; - this.mObeyAps = obeyAps; - this.mAdsFingerprinting = adsFingerprinting; - this.mSecurityFingerprinting = securityFingerprinting; + List<String> serviceProviderEndpoints) { + this.mApsCompliant = apsCompliant; this.mPrivacyPolicy = privacyPolicy; - this.mSecurityEndpoints = securityEndpoints; this.mFirstPartyEndpoints = firstPartyEndpoints; this.mServiceProviderEndpoints = serviceProviderEndpoints; - this.mCategory = category; - this.mEmail = email; - this.mWebsite = website; } /** Creates an on-device DOM element from the {@link SafetyLabels}. */ @Override public List<Element> toOdDomElements(Document doc) { Element appInfoEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_APP_INFO); - if (this.mTitle != null) { - appInfoEle.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_TITLE, mTitle)); - } - if (this.mDescription != null) { - appInfoEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_DESCRIPTION, mDescription)); - } - if (this.mContainsAds != null) { - appInfoEle.appendChild( - XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_CONTAINS_ADS, mContainsAds)); - } - if (this.mObeyAps != null) { - appInfoEle.appendChild( - XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_OBEY_APS, mObeyAps)); - } - if (this.mAdsFingerprinting != null) { + if (this.mApsCompliant != null) { appInfoEle.appendChild( XmlUtils.createOdBooleanEle( - doc, XmlUtils.OD_NAME_ADS_FINGERPRINTING, mAdsFingerprinting)); - } - if (this.mSecurityFingerprinting != null) { - appInfoEle.appendChild( - XmlUtils.createOdBooleanEle( - doc, - XmlUtils.OD_NAME_SECURITY_FINGERPRINTING, - mSecurityFingerprinting)); + doc, XmlUtils.OD_NAME_APS_COMPLIANT, mApsCompliant)); } if (this.mPrivacyPolicy != null) { appInfoEle.appendChild( XmlUtils.createOdStringEle( doc, XmlUtils.OD_NAME_PRIVACY_POLICY, mPrivacyPolicy)); } - if (this.mSecurityEndpoints != null) { - appInfoEle.appendChild( - XmlUtils.createOdArray( - doc, - XmlUtils.OD_TAG_STRING_ARRAY, - XmlUtils.OD_NAME_SECURITY_ENDPOINT, - mSecurityEndpoints)); - } if (this.mFirstPartyEndpoints != null) { appInfoEle.appendChild( XmlUtils.createOdArray( doc, XmlUtils.OD_TAG_STRING_ARRAY, - XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINT, + XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS, mFirstPartyEndpoints)); } if (this.mServiceProviderEndpoints != null) { @@ -125,21 +68,9 @@ public class AppInfo implements AslMarshallable { XmlUtils.createOdArray( doc, XmlUtils.OD_TAG_STRING_ARRAY, - XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINT, + XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS, mServiceProviderEndpoints)); } - if (this.mCategory != null) { - appInfoEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_CATEGORY, this.mCategory)); - } - if (this.mEmail != null) { - appInfoEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, this.mEmail)); - } - if (this.mWebsite != null) { - appInfoEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, this.mWebsite)); - } return XmlUtils.listOf(appInfoEle); } @@ -147,54 +78,28 @@ public class AppInfo implements AslMarshallable { @Override public List<Element> toHrDomElements(Document doc) { Element appInfoEle = doc.createElement(XmlUtils.HR_TAG_APP_INFO); - if (this.mTitle != null) { - appInfoEle.setAttribute(XmlUtils.HR_ATTR_TITLE, this.mTitle); - } - if (this.mDescription != null) { - appInfoEle.setAttribute(XmlUtils.HR_ATTR_DESCRIPTION, this.mDescription); - } - if (this.mContainsAds != null) { - appInfoEle.setAttribute( - XmlUtils.HR_ATTR_CONTAINS_ADS, String.valueOf(this.mContainsAds)); - } - if (this.mObeyAps != null) { - appInfoEle.setAttribute(XmlUtils.HR_ATTR_OBEY_APS, String.valueOf(this.mObeyAps)); - } - if (this.mAdsFingerprinting != null) { + if (this.mApsCompliant != null) { appInfoEle.setAttribute( - XmlUtils.HR_ATTR_ADS_FINGERPRINTING, String.valueOf(this.mAdsFingerprinting)); - } - if (this.mSecurityFingerprinting != null) { - appInfoEle.setAttribute( - XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING, - String.valueOf(this.mSecurityFingerprinting)); + XmlUtils.HR_ATTR_APS_COMPLIANT, String.valueOf(this.mApsCompliant)); } if (this.mPrivacyPolicy != null) { appInfoEle.setAttribute(XmlUtils.HR_ATTR_PRIVACY_POLICY, this.mPrivacyPolicy); } - if (this.mSecurityEndpoints != null) { - appInfoEle.setAttribute( - XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, String.join("|", this.mSecurityEndpoints)); - } + if (this.mFirstPartyEndpoints != null) { - appInfoEle.setAttribute( - XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS, - String.join("|", this.mFirstPartyEndpoints)); + appInfoEle.appendChild( + XmlUtils.createHrArray( + doc, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, mFirstPartyEndpoints)); } + if (this.mServiceProviderEndpoints != null) { - appInfoEle.setAttribute( - XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS, - String.join("|", this.mServiceProviderEndpoints)); - } - if (this.mCategory != null) { - appInfoEle.setAttribute(XmlUtils.HR_ATTR_CATEGORY, this.mCategory); - } - if (this.mEmail != null) { - appInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, this.mEmail); - } - if (this.mWebsite != null) { - appInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, this.mWebsite); + appInfoEle.appendChild( + XmlUtils.createHrArray( + doc, + XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS, + mServiceProviderEndpoints)); } + return XmlUtils.listOf(appInfoEle); } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java index c5069619e069..6ad202765218 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java @@ -35,43 +35,19 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> { return null; } - String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE, true); - String description = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION, true); - Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS, true); - Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS, true); - Boolean adsFingerprinting = - XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING, true); - Boolean securityFingerprinting = - XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING, true); + Boolean apsCompliant = + XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_APS_COMPLIANT, true); String privacyPolicy = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY, true); - List<String> securityEndpoints = - XmlUtils.getPipelineSplitAttr( - appInfoEle, XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, true); List<String> firstPartyEndpoints = - XmlUtils.getPipelineSplitAttr( - appInfoEle, XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS, true); + XmlUtils.getHrItemsAsStrings( + appInfoEle, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, true); List<String> serviceProviderEndpoints = - XmlUtils.getPipelineSplitAttr( - appInfoEle, XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS, true); - String category = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY, true); - String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL, true); - String website = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, false); + XmlUtils.getHrItemsAsStrings( + appInfoEle, XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS, true); return new AppInfo( - title, - description, - containsAds, - obeyAps, - adsFingerprinting, - securityFingerprinting, - privacyPolicy, - securityEndpoints, - firstPartyEndpoints, - serviceProviderEndpoints, - category, - email, - website); + apsCompliant, privacyPolicy, firstPartyEndpoints, serviceProviderEndpoints); } /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ @@ -83,42 +59,17 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> { return null; } - String title = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_TITLE, true); - String description = - XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_DESCRIPTION, true); - Boolean containsAds = - XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_CONTAINS_ADS, true); - Boolean obeyAps = XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_OBEY_APS, true); - Boolean adsFingerprinting = - XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_ADS_FINGERPRINTING, true); - Boolean securityFingerprinting = - XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_SECURITY_FINGERPRINTING, true); + Boolean apsCompliant = + XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_APS_COMPLIANT, true); String privacyPolicy = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_PRIVACY_POLICY, true); - List<String> securityEndpoints = - XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_SECURITY_ENDPOINT, true); List<String> firstPartyEndpoints = - XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINT, true); + XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS, true); List<String> serviceProviderEndpoints = XmlUtils.getOdStringArray( - appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINT, true); - String category = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_CATEGORY, true); - String email = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_EMAIL, true); - String website = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_WEBSITE, false); + appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS, true); return new AppInfo( - title, - description, - containsAds, - obeyAps, - adsFingerprinting, - securityFingerprinting, - privacyPolicy, - securityEndpoints, - firstPartyEndpoints, - serviceProviderEndpoints, - category, - email, - website); + apsCompliant, privacyPolicy, firstPartyEndpoints, serviceProviderEndpoints); } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java index ba0e3db52027..3c93c88cd060 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java @@ -24,6 +24,7 @@ import org.w3c.dom.Element; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Data label representation with data shared and data collected maps containing zero or more {@link @@ -138,7 +139,7 @@ public class DataLabels implements AslMarshallable { "|", dataType.getPurposes().stream() .map(DataType.Purpose::toString) - .toList())); + .collect(Collectors.toList()))); dataLabelsEle.appendChild(hrDataTypeEle); } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java index d2326d10e176..284a4b804435 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java @@ -25,6 +25,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * Data usage type representation. Types are specific to a {@link DataCategory} and contains @@ -182,7 +183,7 @@ public class DataType implements AslMarshallable { XmlUtils.OD_NAME_PURPOSES, this.getPurposes().stream() .map(p -> String.valueOf(p.getValue())) - .toList())); + .collect(Collectors.toList()))); } maybeAddBoolToOdElement( diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java deleted file mode 100644 index 94fad9607880..000000000000 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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.asllib.marshallable; - -import com.android.asllib.util.XmlUtils; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import java.util.List; - -/** DeveloperInfo representation */ -public class DeveloperInfo implements AslMarshallable { - public enum DeveloperRelationship { - OEM(0), - ODM(1), - SOC(2), - OTA(3), - CARRIER(4), - AOSP(5), - OTHER(6); - - private final int mValue; - - DeveloperRelationship(int value) { - this.mValue = value; - } - - /** Get the int value associated with the DeveloperRelationship. */ - public int getValue() { - return mValue; - } - - /** Get the DeveloperRelationship associated with the int value. */ - public static DeveloperInfo.DeveloperRelationship forValue(int value) { - for (DeveloperInfo.DeveloperRelationship e : values()) { - if (e.getValue() == value) { - return e; - } - } - throw new IllegalArgumentException("No DeveloperRelationship enum for value: " + value); - } - - /** Get the DeveloperRelationship associated with the human-readable String. */ - public static DeveloperInfo.DeveloperRelationship forString(String s) { - for (DeveloperInfo.DeveloperRelationship e : values()) { - if (e.toString().equals(s)) { - return e; - } - } - throw new IllegalArgumentException("No DeveloperRelationship enum for str: " + s); - } - - /** Human-readable String representation of DeveloperRelationship. */ - public String toString() { - return this.name().toLowerCase(); - } - } - - private final String mName; - private final String mEmail; - private final String mAddress; - private final String mCountryRegion; - private final DeveloperRelationship mDeveloperRelationship; - private final String mWebsite; - private final String mAppDeveloperRegistryId; - - public DeveloperInfo( - String name, - String email, - String address, - String countryRegion, - DeveloperRelationship developerRelationship, - String website, - String appDeveloperRegistryId) { - this.mName = name; - this.mEmail = email; - this.mAddress = address; - this.mCountryRegion = countryRegion; - this.mDeveloperRelationship = developerRelationship; - this.mWebsite = website; - this.mAppDeveloperRegistryId = appDeveloperRegistryId; - } - - /** Creates an on-device DOM element from the {@link SafetyLabels}. */ - @Override - public List<Element> toOdDomElements(Document doc) { - Element developerInfoEle = - XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DEVELOPER_INFO); - if (mName != null) { - developerInfoEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_NAME, mName)); - } - if (mEmail != null) { - developerInfoEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, mEmail)); - } - if (mAddress != null) { - developerInfoEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_ADDRESS, mAddress)); - } - if (mCountryRegion != null) { - developerInfoEle.appendChild( - XmlUtils.createOdStringEle( - doc, XmlUtils.OD_NAME_COUNTRY_REGION, mCountryRegion)); - } - if (mDeveloperRelationship != null) { - developerInfoEle.appendChild( - XmlUtils.createOdLongEle( - doc, - XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP, - mDeveloperRelationship.getValue())); - } - if (mWebsite != null) { - developerInfoEle.appendChild( - XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, mWebsite)); - } - if (mAppDeveloperRegistryId != null) { - developerInfoEle.appendChild( - XmlUtils.createOdStringEle( - doc, - XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID, - mAppDeveloperRegistryId)); - } - - return XmlUtils.listOf(developerInfoEle); - } - - /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ - @Override - public List<Element> toHrDomElements(Document doc) { - Element developerInfoEle = doc.createElement(XmlUtils.HR_TAG_DEVELOPER_INFO); - if (mName != null) { - developerInfoEle.setAttribute(XmlUtils.HR_ATTR_NAME, mName); - } - if (mEmail != null) { - developerInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, mEmail); - } - if (mAddress != null) { - developerInfoEle.setAttribute(XmlUtils.HR_ATTR_ADDRESS, mAddress); - } - if (mCountryRegion != null) { - developerInfoEle.setAttribute(XmlUtils.HR_ATTR_COUNTRY_REGION, mCountryRegion); - } - if (mDeveloperRelationship != null) { - developerInfoEle.setAttribute( - XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, mDeveloperRelationship.toString()); - } - if (mWebsite != null) { - developerInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, mWebsite); - } - if (mAppDeveloperRegistryId != null) { - developerInfoEle.setAttribute( - XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, mAppDeveloperRegistryId); - } - - return XmlUtils.listOf(developerInfoEle); - } -} diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java deleted file mode 100644 index 0f3b41cd5d1a..000000000000 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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.asllib.marshallable; - -import com.android.asllib.util.AslgenUtil; -import com.android.asllib.util.MalformedXmlException; -import com.android.asllib.util.XmlUtils; - -import org.w3c.dom.Element; - -import java.util.List; - -public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInfo> { - - /** Creates a {@link DeveloperInfo} from the human-readable DOM element. */ - @Override - public DeveloperInfo createFromHrElements(List<Element> elements) throws MalformedXmlException { - Element developerInfoEle = XmlUtils.getSingleElement(elements); - if (developerInfoEle == null) { - AslgenUtil.logI("No DeveloperInfo found in hr format."); - return null; - } - String name = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_NAME, true); - String email = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_EMAIL, true); - String address = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_ADDRESS, true); - String countryRegion = - XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_COUNTRY_REGION, true); - DeveloperInfo.DeveloperRelationship developerRelationship = - DeveloperInfo.DeveloperRelationship.forString( - XmlUtils.getStringAttr( - developerInfoEle, XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, true)); - String website = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_WEBSITE, false); - String appDeveloperRegistryId = - XmlUtils.getStringAttr( - developerInfoEle, XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, false); - - return new DeveloperInfo( - name, - email, - address, - countryRegion, - developerRelationship, - website, - appDeveloperRegistryId); - } - - /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ - @Override - public DeveloperInfo createFromOdElements(List<Element> elements) throws MalformedXmlException { - Element developerInfoEle = XmlUtils.getSingleElement(elements); - if (developerInfoEle == null) { - AslgenUtil.logI("No DeveloperInfo found in od format."); - return null; - } - String name = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_NAME, true); - String email = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_EMAIL, true); - String address = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_ADDRESS, true); - String countryRegion = - XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_COUNTRY_REGION, true); - DeveloperInfo.DeveloperRelationship developerRelationship = - DeveloperInfo.DeveloperRelationship.forValue( - (int) - (long) - XmlUtils.getOdLongEle( - developerInfoEle, - XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP, - true)); - String website = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_WEBSITE, false); - String appDeveloperRegistryId = - XmlUtils.getOdStringEle( - developerInfoEle, XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID, false); - - return new DeveloperInfo( - name, - email, - address, - countryRegion, - developerRelationship, - website, - appDeveloperRegistryId); - } -} diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java index 6af80715f7c1..2a4e130981af 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java @@ -25,21 +25,10 @@ import java.util.List; /** Safety Label representation containing zero or more {@link DataCategory} for data shared */ public class SafetyLabels implements AslMarshallable { - - private final Long mVersion; private final DataLabels mDataLabels; - private final SecurityLabels mSecurityLabels; - private final ThirdPartyVerification mThirdPartyVerification; - public SafetyLabels( - Long version, - DataLabels dataLabels, - SecurityLabels securityLabels, - ThirdPartyVerification thirdPartyVerification) { - this.mVersion = version; + public SafetyLabels(DataLabels dataLabels) { this.mDataLabels = dataLabels; - this.mSecurityLabels = securityLabels; - this.mThirdPartyVerification = thirdPartyVerification; } /** Returns the data label for the safety label */ @@ -47,27 +36,14 @@ public class SafetyLabels implements AslMarshallable { return mDataLabels; } - /** Gets the version of the {@link SafetyLabels}. */ - public Long getVersion() { - return mVersion; - } - /** Creates an on-device DOM element from the {@link SafetyLabels}. */ @Override public List<Element> toOdDomElements(Document doc) { Element safetyLabelsEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS); - safetyLabelsEle.appendChild( - XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion)); if (mDataLabels != null) { XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc)); } - if (mSecurityLabels != null) { - XmlUtils.appendChildren(safetyLabelsEle, mSecurityLabels.toOdDomElements(doc)); - } - if (mThirdPartyVerification != null) { - XmlUtils.appendChildren(safetyLabelsEle, mThirdPartyVerification.toOdDomElements(doc)); - } return XmlUtils.listOf(safetyLabelsEle); } @@ -75,17 +51,10 @@ public class SafetyLabels implements AslMarshallable { @Override public List<Element> toHrDomElements(Document doc) { Element safetyLabelsEle = doc.createElement(XmlUtils.HR_TAG_SAFETY_LABELS); - safetyLabelsEle.setAttribute(XmlUtils.HR_ATTR_VERSION, String.valueOf(mVersion)); if (mDataLabels != null) { XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toHrDomElements(doc)); } - if (mSecurityLabels != null) { - XmlUtils.appendChildren(safetyLabelsEle, mSecurityLabels.toHrDomElements(doc)); - } - if (mThirdPartyVerification != null) { - XmlUtils.appendChildren(safetyLabelsEle, mThirdPartyVerification.toHrDomElements(doc)); - } return XmlUtils.listOf(safetyLabelsEle); } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java index 2644b435311b..2738337b7080 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java @@ -34,7 +34,6 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> AslgenUtil.logI("No SafetyLabels found in hr format."); return null; } - long version = XmlUtils.tryGetVersion(safetyLabelsEle); DataLabels dataLabels = new DataLabelsFactory() @@ -44,23 +43,7 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> safetyLabelsEle, XmlUtils.HR_TAG_DATA_LABELS, false))); - SecurityLabels securityLabels = - new SecurityLabelsFactory() - .createFromHrElements( - XmlUtils.listOf( - XmlUtils.getSingleChildElement( - safetyLabelsEle, - XmlUtils.HR_TAG_SECURITY_LABELS, - false))); - ThirdPartyVerification thirdPartyVerification = - new ThirdPartyVerificationFactory() - .createFromHrElements( - XmlUtils.listOf( - XmlUtils.getSingleChildElement( - safetyLabelsEle, - XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION, - false))); - return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification); + return new SafetyLabels(dataLabels); } /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ @@ -71,7 +54,6 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> AslgenUtil.logI("No SafetyLabels found in od format."); return null; } - Long version = XmlUtils.getOdLongEle(safetyLabelsEle, XmlUtils.OD_NAME_VERSION, true); DataLabels dataLabels = new DataLabelsFactory() @@ -81,22 +63,7 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> safetyLabelsEle, XmlUtils.OD_NAME_DATA_LABELS, false))); - SecurityLabels securityLabels = - new SecurityLabelsFactory() - .createFromOdElements( - XmlUtils.listOf( - XmlUtils.getOdPbundleWithName( - safetyLabelsEle, - XmlUtils.OD_NAME_SECURITY_LABELS, - false))); - ThirdPartyVerification thirdPartyVerification = - new ThirdPartyVerificationFactory() - .createFromOdElements( - XmlUtils.listOf( - XmlUtils.getOdPbundleWithName( - safetyLabelsEle, - XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION, - false))); - return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification); + + return new SafetyLabels(dataLabels); } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java index 6a8700a10d3f..9f789f0b9c1c 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java @@ -23,22 +23,14 @@ import org.w3c.dom.Element; import java.util.List; -/** TransparencyInfo representation containing {@link DeveloperInfo} and {@link AppInfo} */ +/** TransparencyInfo representation containing {@link AppInfo} */ public class TransparencyInfo implements AslMarshallable { - - private final DeveloperInfo mDeveloperInfo; private final AppInfo mAppInfo; - public TransparencyInfo(DeveloperInfo developerInfo, AppInfo appInfo) { - this.mDeveloperInfo = developerInfo; + public TransparencyInfo(AppInfo appInfo) { this.mAppInfo = appInfo; } - /** Gets the {@link DeveloperInfo} of the {@link TransparencyInfo}. */ - public DeveloperInfo getDeveloperInfo() { - return mDeveloperInfo; - } - /** Gets the {@link AppInfo} of the {@link TransparencyInfo}. */ public AppInfo getAppInfo() { return mAppInfo; @@ -49,9 +41,6 @@ public class TransparencyInfo implements AslMarshallable { public List<Element> toOdDomElements(Document doc) { Element transparencyInfoEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_TRANSPARENCY_INFO); - if (mDeveloperInfo != null) { - XmlUtils.appendChildren(transparencyInfoEle, mDeveloperInfo.toOdDomElements(doc)); - } if (mAppInfo != null) { XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toOdDomElements(doc)); } @@ -62,9 +51,6 @@ public class TransparencyInfo implements AslMarshallable { @Override public List<Element> toHrDomElements(Document doc) { Element transparencyInfoEle = doc.createElement(XmlUtils.HR_TAG_TRANSPARENCY_INFO); - if (mDeveloperInfo != null) { - XmlUtils.appendChildren(transparencyInfoEle, mDeveloperInfo.toHrDomElements(doc)); - } if (mAppInfo != null) { XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toHrDomElements(doc)); } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java index 94c564087918..40f2872e4380 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java @@ -36,18 +36,11 @@ public class TransparencyInfoFactory implements AslMarshallableFactory<Transpare return null; } - Element developerInfoEle = - XmlUtils.getSingleChildElement( - transparencyInfoEle, XmlUtils.HR_TAG_DEVELOPER_INFO, false); - DeveloperInfo developerInfo = - new DeveloperInfoFactory().createFromHrElements(XmlUtils.listOf(developerInfoEle)); - Element appInfoEle = - XmlUtils.getSingleChildElement( - transparencyInfoEle, XmlUtils.HR_TAG_APP_INFO, false); + XmlUtils.getSingleChildElement(transparencyInfoEle, XmlUtils.HR_TAG_APP_INFO, true); AppInfo appInfo = new AppInfoFactory().createFromHrElements(XmlUtils.listOf(appInfoEle)); - return new TransparencyInfo(developerInfo, appInfo); + return new TransparencyInfo(appInfo); } /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ @@ -60,17 +53,10 @@ public class TransparencyInfoFactory implements AslMarshallableFactory<Transpare return null; } - Element developerInfoEle = - XmlUtils.getOdPbundleWithName( - transparencyInfoEle, XmlUtils.OD_NAME_DEVELOPER_INFO, false); - DeveloperInfo developerInfo = - new DeveloperInfoFactory().createFromOdElements(XmlUtils.listOf(developerInfoEle)); - Element appInfoEle = - XmlUtils.getOdPbundleWithName( - transparencyInfoEle, XmlUtils.OD_NAME_APP_INFO, false); + XmlUtils.getOdPbundleWithName(transparencyInfoEle, XmlUtils.OD_NAME_APP_INFO, true); AppInfo appInfo = new AppInfoFactory().createFromOdElements(XmlUtils.listOf(appInfoEle)); - return new TransparencyInfo(developerInfo, appInfo); + return new TransparencyInfo(appInfo); } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java index 97cbc3950490..2c1517bdf8ab 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java @@ -25,6 +25,7 @@ import org.w3c.dom.NodeList; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class XmlUtils { public static final String DATA_TYPE_SEPARATOR = "_data_type_"; @@ -42,6 +43,7 @@ public class XmlUtils { public static final String HR_TAG_DATA_COLLECTED = "data-collected"; public static final String HR_TAG_DATA_COLLECTED_EPHEMERAL = "data-collected-ephemeral"; public static final String HR_TAG_DATA_SHARED = "data-shared"; + public static final String HR_TAG_ITEM = "item"; public static final String HR_ATTR_NAME = "name"; public static final String HR_ATTR_EMAIL = "email"; public static final String HR_ATTR_ADDRESS = "address"; @@ -64,12 +66,13 @@ public class XmlUtils { public static final String HR_ATTR_DESCRIPTION = "description"; public static final String HR_ATTR_CONTAINS_ADS = "containsAds"; public static final String HR_ATTR_OBEY_APS = "obeyAps"; + public static final String HR_ATTR_APS_COMPLIANT = "apsCompliant"; public static final String HR_ATTR_ADS_FINGERPRINTING = "adsFingerprinting"; public static final String HR_ATTR_SECURITY_FINGERPRINTING = "securityFingerprinting"; public static final String HR_ATTR_PRIVACY_POLICY = "privacyPolicy"; public static final String HR_ATTR_SECURITY_ENDPOINTS = "securityEndpoints"; - public static final String HR_ATTR_FIRST_PARTY_ENDPOINTS = "firstPartyEndpoints"; - public static final String HR_ATTR_SERVICE_PROVIDER_ENDPOINTS = "serviceProviderEndpoints"; + public static final String HR_TAG_FIRST_PARTY_ENDPOINTS = "first-party-endpoints"; + public static final String HR_TAG_SERVICE_PROVIDER_ENDPOINTS = "service-provider-endpoints"; public static final String HR_ATTR_CATEGORY = "category"; public static final String OD_TAG_BUNDLE = "bundle"; @@ -98,12 +101,13 @@ public class XmlUtils { public static final String OD_NAME_DESCRIPTION = "description"; public static final String OD_NAME_CONTAINS_ADS = "contains_ads"; public static final String OD_NAME_OBEY_APS = "obey_aps"; + public static final String OD_NAME_APS_COMPLIANT = "aps_compliant"; public static final String OD_NAME_ADS_FINGERPRINTING = "ads_fingerprinting"; public static final String OD_NAME_SECURITY_FINGERPRINTING = "security_fingerprinting"; public static final String OD_NAME_PRIVACY_POLICY = "privacy_policy"; - public static final String OD_NAME_SECURITY_ENDPOINT = "security_endpoint"; - public static final String OD_NAME_FIRST_PARTY_ENDPOINT = "first_party_endpoint"; - public static final String OD_NAME_SERVICE_PROVIDER_ENDPOINT = "service_provider_endpoint"; + public static final String OD_NAME_SECURITY_ENDPOINT = "security_endpoints"; + public static final String OD_NAME_FIRST_PARTY_ENDPOINTS = "first_party_endpoints"; + public static final String OD_NAME_SERVICE_PROVIDER_ENDPOINTS = "service_provider_endpoints"; public static final String OD_NAME_CATEGORY = "category"; public static final String OD_NAME_VERSION = "version"; public static final String OD_NAME_URL = "url"; @@ -128,7 +132,9 @@ public class XmlUtils { /** Gets the top-level children with the tag name.. */ public static List<Element> getChildrenByTagName(Node parentEle, String tagName) { var elements = XmlUtils.asElementList(parentEle.getChildNodes()); - return elements.stream().filter(e -> e.getTagName().equals(tagName)).toList(); + return elements.stream() + .filter(e -> e.getTagName().equals(tagName)) + .collect(Collectors.toList()); } /** @@ -237,7 +243,18 @@ public class XmlUtils { return ele; } - /** Create OD style array DOM Element, which can represent any time but is stored as Strings. */ + /** Create HR style array DOM Element. */ + public static Element createHrArray(Document doc, String arrayTagName, List<String> arrayVals) { + Element arrEle = doc.createElement(arrayTagName); + for (String s : arrayVals) { + Element itemEle = doc.createElement(XmlUtils.HR_TAG_ITEM); + itemEle.setTextContent(s); + arrEle.appendChild(itemEle); + } + return arrEle; + } + + /** Create OD style array DOM Element, which can represent any type but is stored as Strings. */ public static Element createOdArray( Document doc, String arrayTag, String arrayName, List<String> arrayVals) { Element arrEle = doc.createElement(arrayTag); @@ -272,7 +289,8 @@ public class XmlUtils { /** Gets a pipeline-split attribute. */ public static List<String> getPipelineSplitAttr(Element ele, String attrName, boolean required) throws MalformedXmlException { - List<String> list = Arrays.stream(ele.getAttribute(attrName).split("\\|")).toList(); + List<String> list = + Arrays.stream(ele.getAttribute(attrName).split("\\|")).collect(Collectors.toList()); if ((list.isEmpty() || list.get(0).isEmpty()) && required) { throw new MalformedXmlException( String.format( @@ -301,7 +319,7 @@ public class XmlUtils { List<Element> boolEles = XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_BOOLEAN).stream() .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) - .toList(); + .collect(Collectors.toList()); if (boolEles.size() > 1) { throw new MalformedXmlException( String.format( @@ -332,7 +350,7 @@ public class XmlUtils { List<Element> longEles = XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_LONG).stream() .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) - .toList(); + .collect(Collectors.toList()); if (longEles.size() > 1) { throw new MalformedXmlException( String.format( @@ -363,7 +381,7 @@ public class XmlUtils { List<Element> eles = XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_STRING).stream() .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) - .toList(); + .collect(Collectors.toList()); if (eles.size() > 1) { throw new MalformedXmlException( String.format( @@ -391,7 +409,7 @@ public class XmlUtils { List<Element> eles = XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_PBUNDLE_AS_MAP).stream() .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) - .toList(); + .collect(Collectors.toList()); if (eles.size() > 1) { throw new MalformedXmlException( String.format( @@ -435,7 +453,7 @@ public class XmlUtils { List<Element> intArrayEles = XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_INT_ARRAY).stream() .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) - .toList(); + .collect(Collectors.toList()); if (intArrayEles.size() > 1) { throw new MalformedXmlException( String.format("Found more than one %s in %s.", nameName, ele.getTagName())); @@ -456,13 +474,39 @@ public class XmlUtils { return ints; } + /** Gets human-readable style String array. */ + public static List<String> getHrItemsAsStrings( + Element parent, String elementName, boolean required) throws MalformedXmlException { + + List<Element> arrayEles = XmlUtils.getChildrenByTagName(parent, elementName); + if (arrayEles.size() > 1) { + throw new MalformedXmlException( + String.format( + "Found more than one %s in %s.", elementName, parent.getTagName())); + } + if (arrayEles.isEmpty()) { + if (required) { + throw new MalformedXmlException( + String.format("Found no %s in %s.", elementName, parent.getTagName())); + } + return null; + } + Element arrayEle = arrayEles.get(0); + List<Element> itemEles = XmlUtils.getChildrenByTagName(arrayEle, XmlUtils.HR_TAG_ITEM); + List<String> strs = new ArrayList<String>(); + for (Element itemEle : itemEles) { + strs.add(itemEle.getTextContent()); + } + return strs; + } + /** Gets on-device style String array. */ public static List<String> getOdStringArray(Element ele, String nameName, boolean required) throws MalformedXmlException { List<Element> arrayEles = XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_STRING_ARRAY).stream() .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) - .toList(); + .collect(Collectors.toList()); if (arrayEles.size() > 1) { throw new MalformedXmlException( String.format( diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java index dbeeb496144a..14e65e5e5b2b 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java @@ -20,11 +20,8 @@ import com.android.asllib.marshallable.AndroidSafetyLabelTest; import com.android.asllib.marshallable.AppInfoTest; import com.android.asllib.marshallable.DataLabelsTest; import com.android.asllib.marshallable.DataTypeEqualityTest; -import com.android.asllib.marshallable.DeveloperInfoTest; import com.android.asllib.marshallable.SafetyLabelsTest; -import com.android.asllib.marshallable.SecurityLabelsTest; import com.android.asllib.marshallable.SystemAppSafetyLabelTest; -import com.android.asllib.marshallable.ThirdPartyVerificationTest; import com.android.asllib.marshallable.TransparencyInfoTest; import org.junit.runner.RunWith; @@ -35,14 +32,10 @@ import org.junit.runners.Suite; AslgenTests.class, AndroidSafetyLabelTest.class, AppInfoTest.class, - // DataCategoryTest.class, DataLabelsTest.class, DataTypeEqualityTest.class, - DeveloperInfoTest.class, SafetyLabelsTest.class, - SecurityLabelsTest.class, SystemAppSafetyLabelTest.class, - ThirdPartyVerificationTest.class, TransparencyInfoTest.class }) public class AllTests {} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java index 9e91c6f22641..d823c482adfe 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertThrows; import com.android.asllib.testutils.TestUtils; import com.android.asllib.util.MalformedXmlException; +import com.android.asllib.util.XmlUtils; import org.junit.Before; import org.junit.Test; @@ -34,35 +35,15 @@ public class AppInfoTest { private static final String APP_INFO_HR_PATH = "com/android/asllib/appinfo/hr"; private static final String APP_INFO_OD_PATH = "com/android/asllib/appinfo/od"; public static final List<String> REQUIRED_FIELD_NAMES = - List.of( - "title", - "description", - "containsAds", - "obeyAps", - "adsFingerprinting", - "securityFingerprinting", - "privacyPolicy", - "securityEndpoints", - "firstPartyEndpoints", - "serviceProviderEndpoints", - "category", - "email"); + List.of("apsCompliant", "privacyPolicy"); public static final List<String> REQUIRED_FIELD_NAMES_OD = - List.of( - "title", - "description", - "contains_ads", - "obey_aps", - "ads_fingerprinting", - "security_fingerprinting", - "privacy_policy", - "security_endpoint", - "first_party_endpoint", - "service_provider_endpoint", - "category", - "email"); - public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website"); - public static final List<String> OPTIONAL_FIELD_NAMES_OD = List.of("website"); + List.of("aps_compliant", "privacy_policy"); + public static final List<String> REQUIRED_CHILD_NAMES = + List.of("first-party-endpoints", "service-provider-endpoints"); + public static final List<String> REQUIRED_CHILD_NAMES_OD = + List.of("first_party_endpoints", "service_provider_endpoints"); + public static final List<String> OPTIONAL_FIELD_NAMES = List.of(); + public static final List<String> OPTIONAL_FIELD_NAMES_OD = List.of(); private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml"; @@ -110,6 +91,34 @@ public class AppInfoTest { } } + /** Tests missing required child fails. */ + @Test + public void testMissingRequiredChild() throws Exception { + System.out.println("Starting testMissingRequiredFields"); + for (String reqChildName : REQUIRED_CHILD_NAMES) { + System.out.println("testing missing required child hr: " + reqChildName); + var appInfoEle = + TestUtils.getElementsFromResource( + Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME)); + var child = XmlUtils.getChildrenByTagName(appInfoEle.get(0), reqChildName).get(0); + appInfoEle.get(0).removeChild(child); + assertThrows( + MalformedXmlException.class, + () -> new AppInfoFactory().createFromHrElements(appInfoEle)); + } + + for (String reqField : REQUIRED_CHILD_NAMES_OD) { + System.out.println("testing missing required child od: " + reqField); + var appInfoEle = + TestUtils.getElementsFromResource( + Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME)); + TestUtils.removeOdChildEleWithName(appInfoEle.get(0), reqField); + assertThrows( + MalformedXmlException.class, + () -> new AppInfoFactory().createFromOdElements(appInfoEle)); + } + } + /** Tests missing optional fields passes. */ @Test public void testMissingOptionalFields() throws Exception { diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java deleted file mode 100644 index 72e8d654b542..000000000000 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.asllib.marshallable; - -import static org.junit.Assert.assertThrows; - -import com.android.asllib.testutils.TestUtils; -import com.android.asllib.util.MalformedXmlException; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.nio.file.Paths; -import java.util.List; - -@RunWith(JUnit4.class) -public class DeveloperInfoTest { - private static final String DEVELOPER_INFO_HR_PATH = "com/android/asllib/developerinfo/hr"; - private static final String DEVELOPER_INFO_OD_PATH = "com/android/asllib/developerinfo/od"; - public static final List<String> REQUIRED_FIELD_NAMES = - List.of("address", "countryRegion", "email", "name", "relationship"); - public static final List<String> REQUIRED_FIELD_NAMES_OD = - List.of("address", "country_region", "email", "name", "relationship"); - public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website", "registryId"); - public static final List<String> OPTIONAL_FIELD_NAMES_OD = - List.of("website", "app_developer_registry_id"); - - private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml"; - - /** Logic for setting up tests (empty if not yet needed). */ - public static void main(String[] params) throws Exception {} - - @Before - public void setUp() throws Exception { - System.out.println("set up."); - } - - /** Test for all fields valid. */ - @Test - public void testAllFieldsValid() throws Exception { - System.out.println("starting testAllFieldsValid."); - testHrToOdDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME); - testOdToHrDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME); - } - - /** Tests missing required fields fails. */ - @Test - public void testMissingRequiredFields() throws Exception { - System.out.println("Starting testMissingRequiredFields"); - for (String reqField : REQUIRED_FIELD_NAMES) { - System.out.println("testing missing required field: " + reqField); - var developerInfoEle = - TestUtils.getElementsFromResource( - Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME)); - developerInfoEle.get(0).removeAttribute(reqField); - - assertThrows( - MalformedXmlException.class, - () -> new DeveloperInfoFactory().createFromHrElements(developerInfoEle)); - } - - for (String reqField : REQUIRED_FIELD_NAMES_OD) { - System.out.println("testing missing required field od: " + reqField); - var developerInfoEle = - TestUtils.getElementsFromResource( - Paths.get(DEVELOPER_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME)); - TestUtils.removeOdChildEleWithName(developerInfoEle.get(0), reqField); - - assertThrows( - MalformedXmlException.class, - () -> new DeveloperInfoFactory().createFromOdElements(developerInfoEle)); - } - } - - /** Tests missing optional fields passes. */ - @Test - public void testMissingOptionalFields() throws Exception { - for (String optField : OPTIONAL_FIELD_NAMES) { - var developerInfoEle = - TestUtils.getElementsFromResource( - Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME)); - developerInfoEle.get(0).removeAttribute(optField); - DeveloperInfo developerInfo = - new DeveloperInfoFactory().createFromHrElements(developerInfoEle); - developerInfo.toOdDomElements(TestUtils.document()); - } - - for (String optField : OPTIONAL_FIELD_NAMES_OD) { - var developerInfoEle = - TestUtils.getElementsFromResource( - Paths.get(DEVELOPER_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME)); - TestUtils.removeOdChildEleWithName(developerInfoEle.get(0), optField); - DeveloperInfo developerInfo = - new DeveloperInfoFactory().createFromOdElements(developerInfoEle); - developerInfo.toHrDomElements(TestUtils.document()); - } - } - - private void testHrToOdDeveloperInfo(String fileName) throws Exception { - TestUtils.testHrToOd( - TestUtils.document(), - new DeveloperInfoFactory(), - DEVELOPER_INFO_HR_PATH, - DEVELOPER_INFO_OD_PATH, - fileName); - } - - private void testOdToHrDeveloperInfo(String fileName) throws Exception { - TestUtils.testOdToHr( - TestUtils.document(), - new DeveloperInfoFactory(), - DEVELOPER_INFO_OD_PATH, - DEVELOPER_INFO_HR_PATH, - fileName); - } -} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java index bba6b548beaf..19d1626f7054 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java @@ -28,26 +28,14 @@ public class SafetyLabelsTest { private static final String SAFETY_LABELS_HR_PATH = "com/android/asllib/safetylabels/hr"; private static final String SAFETY_LABELS_OD_PATH = "com/android/asllib/safetylabels/od"; - private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml"; private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml"; private static final String WITH_DATA_LABELS_FILE_NAME = "with-data-labels.xml"; - private static final String WITH_SECURITY_LABELS_FILE_NAME = "with-security-labels.xml"; - private static final String WITH_THIRD_PARTY_VERIFICATION_FILE_NAME = - "with-third-party-verification.xml"; @Before public void setUp() throws Exception { System.out.println("set up."); } - /** Test for safety labels missing version. */ - @Test - public void testSafetyLabelsMissingVersion() throws Exception { - System.out.println("starting testSafetyLabelsMissingVersion."); - hrToOdExpectException(MISSING_VERSION_FILE_NAME); - odToHrExpectException(MISSING_VERSION_FILE_NAME); - } - /** Test for safety labels valid empty. */ @Test public void testSafetyLabelsValidEmptyFile() throws Exception { @@ -64,22 +52,6 @@ public class SafetyLabelsTest { testOdToHrSafetyLabels(WITH_DATA_LABELS_FILE_NAME); } - /** Test for safety labels with security labels. */ - @Test - public void testSafetyLabelsWithSecurityLabels() throws Exception { - System.out.println("starting testSafetyLabelsWithSecurityLabels."); - testHrToOdSafetyLabels(WITH_SECURITY_LABELS_FILE_NAME); - testOdToHrSafetyLabels(WITH_SECURITY_LABELS_FILE_NAME); - } - - /** Test for safety labels with third party verification. */ - @Test - public void testSafetyLabelsWithThirdPartyVerification() throws Exception { - System.out.println("starting testSafetyLabelsWithThirdPartyVerification."); - testHrToOdSafetyLabels(WITH_THIRD_PARTY_VERIFICATION_FILE_NAME); - testOdToHrSafetyLabels(WITH_THIRD_PARTY_VERIFICATION_FILE_NAME); - } - private void hrToOdExpectException(String fileName) { TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName); } diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java deleted file mode 100644 index a940bc63c685..000000000000 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.asllib.marshallable; - - -import com.android.asllib.testutils.TestUtils; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.nio.file.Paths; -import java.util.List; - -@RunWith(JUnit4.class) -public class SecurityLabelsTest { - private static final String SECURITY_LABELS_HR_PATH = "com/android/asllib/securitylabels/hr"; - private static final String SECURITY_LABELS_OD_PATH = "com/android/asllib/securitylabels/od"; - - public static final List<String> OPTIONAL_FIELD_NAMES = - List.of("isDataDeletable", "isDataEncrypted"); - public static final List<String> OPTIONAL_FIELD_NAMES_OD = - List.of("is_data_deletable", "is_data_encrypted"); - - private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml"; - - /** Logic for setting up tests (empty if not yet needed). */ - public static void main(String[] params) throws Exception {} - - @Before - public void setUp() throws Exception { - System.out.println("set up."); - } - - /** Test for all fields valid. */ - @Test - public void testAllFieldsValid() throws Exception { - System.out.println("starting testAllFieldsValid."); - testHrToOdSecurityLabels(ALL_FIELDS_VALID_FILE_NAME); - testOdToHrSecurityLabels(ALL_FIELDS_VALID_FILE_NAME); - } - - /** Tests missing optional fields passes. */ - @Test - public void testMissingOptionalFields() throws Exception { - for (String optField : OPTIONAL_FIELD_NAMES) { - var ele = - TestUtils.getElementsFromResource( - Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME)); - ele.get(0).removeAttribute(optField); - SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElements(ele); - securityLabels.toOdDomElements(TestUtils.document()); - } - for (String optField : OPTIONAL_FIELD_NAMES_OD) { - var ele = - TestUtils.getElementsFromResource( - Paths.get(SECURITY_LABELS_OD_PATH, ALL_FIELDS_VALID_FILE_NAME)); - TestUtils.removeOdChildEleWithName(ele.get(0), optField); - SecurityLabels securityLabels = new SecurityLabelsFactory().createFromOdElements(ele); - securityLabels.toHrDomElements(TestUtils.document()); - } - } - - private void testHrToOdSecurityLabels(String fileName) throws Exception { - TestUtils.testHrToOd( - TestUtils.document(), - new SecurityLabelsFactory(), - SECURITY_LABELS_HR_PATH, - SECURITY_LABELS_OD_PATH, - fileName); - } - - private void testOdToHrSecurityLabels(String fileName) throws Exception { - TestUtils.testOdToHr( - TestUtils.document(), - new SecurityLabelsFactory(), - SECURITY_LABELS_OD_PATH, - SECURITY_LABELS_HR_PATH, - fileName); - } -} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java deleted file mode 100644 index ec86d0f863af..000000000000 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.asllib.marshallable; - -import com.android.asllib.testutils.TestUtils; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class ThirdPartyVerificationTest { - private static final String THIRD_PARTY_VERIFICATION_HR_PATH = - "com/android/asllib/thirdpartyverification/hr"; - private static final String THIRD_PARTY_VERIFICATION_OD_PATH = - "com/android/asllib/thirdpartyverification/od"; - - private static final String VALID_FILE_NAME = "valid.xml"; - private static final String MISSING_URL_FILE_NAME = "missing-url.xml"; - - /** Logic for setting up tests (empty if not yet needed). */ - public static void main(String[] params) throws Exception {} - - @Before - public void setUp() throws Exception { - System.out.println("set up."); - } - - /** Test for valid. */ - @Test - public void testValid() throws Exception { - System.out.println("starting testValid."); - testHrToOdThirdPartyVerification(VALID_FILE_NAME); - testOdToHrThirdPartyVerification(VALID_FILE_NAME); - } - - /** Tests missing url. */ - @Test - public void testMissingUrl() throws Exception { - System.out.println("starting testMissingUrl."); - hrToOdExpectException(MISSING_URL_FILE_NAME); - odToHrExpectException(MISSING_URL_FILE_NAME); - } - - private void hrToOdExpectException(String fileName) { - TestUtils.hrToOdExpectException( - new ThirdPartyVerificationFactory(), THIRD_PARTY_VERIFICATION_HR_PATH, fileName); - } - - private void odToHrExpectException(String fileName) { - TestUtils.odToHrExpectException( - new ThirdPartyVerificationFactory(), THIRD_PARTY_VERIFICATION_OD_PATH, fileName); - } - - private void testHrToOdThirdPartyVerification(String fileName) throws Exception { - TestUtils.testHrToOd( - TestUtils.document(), - new ThirdPartyVerificationFactory(), - THIRD_PARTY_VERIFICATION_HR_PATH, - THIRD_PARTY_VERIFICATION_OD_PATH, - fileName); - } - - private void testOdToHrThirdPartyVerification(String fileName) throws Exception { - TestUtils.testOdToHr( - TestUtils.document(), - new ThirdPartyVerificationFactory(), - THIRD_PARTY_VERIFICATION_OD_PATH, - THIRD_PARTY_VERIFICATION_HR_PATH, - fileName); - } -} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java index f49424061427..8a0b35eac7c8 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java @@ -29,9 +29,6 @@ public class TransparencyInfoTest { "com/android/asllib/transparencyinfo/hr"; private static final String TRANSPARENCY_INFO_OD_PATH = "com/android/asllib/transparencyinfo/od"; - - private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml"; - private static final String WITH_DEVELOPER_INFO_FILE_NAME = "with-developer-info.xml"; private static final String WITH_APP_INFO_FILE_NAME = "with-app-info.xml"; @Before @@ -39,22 +36,6 @@ public class TransparencyInfoTest { System.out.println("set up."); } - /** Test for transparency info valid empty. */ - @Test - public void testTransparencyInfoValidEmptyFile() throws Exception { - System.out.println("starting testTransparencyInfoValidEmptyFile."); - testHrToOdTransparencyInfo(VALID_EMPTY_FILE_NAME); - testOdToHrTransparencyInfo(VALID_EMPTY_FILE_NAME); - } - - /** Test for transparency info with developer info. */ - @Test - public void testTransparencyInfoWithDeveloperInfo() throws Exception { - System.out.println("starting testTransparencyInfoWithDeveloperInfo."); - testHrToOdTransparencyInfo(WITH_DEVELOPER_INFO_FILE_NAME); - testOdToHrTransparencyInfo(WITH_DEVELOPER_INFO_FILE_NAME); - } - /** Test for transparency info with app info. */ @Test public void testTransparencyInfoWithAppInfo() throws Exception { diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml index 53794a1d1c80..03e71d28f4cc 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml @@ -1,4 +1,4 @@ <app-metadata-bundles version="123456"> - <safety-labels version="12345"> + <safety-labels> </safety-labels> </app-metadata-bundles>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml index 00bcfa80e9b1..a00ef6565cf4 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml @@ -1,4 +1,15 @@ <app-metadata-bundles version="123456"> -<transparency-info> -</transparency-info> + <transparency-info> + <app-info + apsCompliant="false" + privacyPolicy="www.example.com"> + <first-party-endpoints> + <item>url1</item> + </first-party-endpoints> + <service-provider-endpoints> + <item>url55</item> + <item>url56</item> + </service-provider-endpoints> + </app-info> + </transparency-info> </app-metadata-bundles>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml index 74644ed0413c..f00fb26bf1ee 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml @@ -1,6 +1,5 @@ <bundle> <long name="version" value="123456"/> <pbundle_as_map name="safety_labels"> - <long name="version" value="12345"/> </pbundle_as_map> </bundle> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml index 63c5094333cc..d0c8668564bd 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml @@ -1,4 +1,16 @@ <bundle> <long name="version" value="123456"/> - <pbundle_as_map name="transparency_info"/> + <pbundle_as_map name="transparency_info"> + <pbundle_as_map name="app_info"> + <boolean name="aps_compliant" value="false"/> + <string name="privacy_policy" value="www.example.com"/> + <string-array name="first_party_endpoints" num="1"> + <item value="url1"/> + </string-array> + <string-array name="service_provider_endpoints" num="2"> + <item value="url55"/> + <item value="url56"/> + </string-array> + </pbundle_as_map> + </pbundle_as_map> </bundle>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml index 883170a2d36f..0d15efc4eac3 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml @@ -1,14 +1,11 @@ <app-info - title="beervision" - description="a beer app" - containsAds="true" - obeyAps="false" - adsFingerprinting="false" - securityFingerprinting="false" - privacyPolicy="www.example.com" - securityEndpoints="url1|url2|url3" - firstPartyEndpoints="url1" - serviceProviderEndpoints="url55|url56" - category="Food and drink" - email="max@maxloh.com" - website="www.example.com" />
\ No newline at end of file + apsCompliant="false" + privacyPolicy="www.example.com"> + <first-party-endpoints> + <item>url1</item> + </first-party-endpoints> + <service-provider-endpoints> + <item>url55</item> + <item>url56</item> + </service-provider-endpoints> +</app-info>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml index 6e976a3278de..bce51799c7ff 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml @@ -1,25 +1,12 @@ <pbundle_as_map name="app_info"> - <string name="title" value="beervision"/> - <string name="description" value="a beer app"/> - <boolean name="contains_ads" value="true"/> - <boolean name="obey_aps" value="false"/> - <boolean name="ads_fingerprinting" value="false"/> - <boolean name="security_fingerprinting" value="false"/> + <boolean name="aps_compliant" value="false"/> <string name="privacy_policy" value="www.example.com"/> - <string-array name="security_endpoint" num="3"> + <string-array name="first_party_endpoints" num="1"> <item value="url1"/> - <item value="url2"/> - <item value="url3"/> </string-array> - <string-array name="first_party_endpoint" num="1"> - <item value="url1"/> - </string-array> - <string-array name="service_provider_endpoint" num="2"> + <string-array name="service_provider_endpoints" num="2"> <item value="url55"/> <item value="url56"/> </string-array> - <string name="category" value="Food and drink"/> - <string name="email" value="max@maxloh.com"/> - <string name="website" value="www.example.com"/> </pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml deleted file mode 100644 index 762f3bdf7875..000000000000 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml +++ /dev/null @@ -1,2 +0,0 @@ -<safety-labels> -</safety-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml index 7decfd4865b1..92d10ca0349b 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml @@ -1 +1 @@ -<safety-labels version="12345"></safety-labels>
\ No newline at end of file +<safety-labels></safety-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml index 84456daedbd5..ca3d9f066129 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml @@ -1,4 +1,4 @@ -<safety-labels version="12345"> +<safety-labels> <data-labels> <data-shared dataType="location_data_type_approx_location" isSharingOptional="false" diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml index 940e48a68ce8..c96278554865 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml @@ -1,4 +1,4 @@ -<safety-labels version="12345"> +<safety-labels> <security-labels isDataDeletable="true" isDataEncrypted="false" diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml index bfbc5ae70974..08052904e8e5 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml @@ -1,4 +1,4 @@ -<safety-labels version="12345"> +<safety-labels> <third-party-verification url="www.example.com"> </third-party-verification> </safety-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml deleted file mode 100644 index 3fbe3599cd82..000000000000 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml +++ /dev/null @@ -1,2 +0,0 @@ -<pbundle_as_map name="safety_labels"> -</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml index 4f03d88a3bd2..3fbe3599cd82 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml @@ -1,3 +1,2 @@ <pbundle_as_map name="safety_labels"> - <long name="version" value="12345"/> </pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml index fa2a3f841e09..db92e0291ee8 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml @@ -1,5 +1,4 @@ <pbundle_as_map name="safety_labels"> - <long name="version" value="12345"/> <pbundle_as_map name="data_labels"> <pbundle_as_map name="data_shared"> <pbundle_as_map name="location"> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml index b39c562b30d0..924618087b4f 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml @@ -1,5 +1,4 @@ <pbundle_as_map name="safety_labels"> - <long name="version" value="12345"/> <pbundle_as_map name="security_labels"> <boolean name="is_data_deletable" value="true" /> <boolean name="is_data_encrypted" value="false" /> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml index 10653ff5027b..fd435c550a54 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml @@ -1,5 +1,4 @@ <pbundle_as_map name="safety_labels"> - <long name="version" value="12345"/> <pbundle_as_map name="third_party_verification"> <string name="url" value="www.example.com"/> </pbundle_as_map> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml index a7c48fc68cf1..2512ca415d55 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml @@ -1,4 +1,14 @@ <transparency-info> - <app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" /> + <app-info + apsCompliant="false" + privacyPolicy="www.example.com"> + <first-party-endpoints> + <item>url1</item> + </first-party-endpoints> + <service-provider-endpoints> + <item>url55</item> + <item>url56</item> + </service-provider-endpoints> + </app-info> </transparency-info>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml index b813641f74f8..c7bdd97a356c 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml @@ -1,26 +1,14 @@ <pbundle_as_map name="transparency_info"> <pbundle_as_map name="app_info"> - <string name="title" value="beervision"/> - <string name="description" value="a beer app"/> - <boolean name="contains_ads" value="true"/> - <boolean name="obey_aps" value="false"/> - <boolean name="ads_fingerprinting" value="false"/> - <boolean name="security_fingerprinting" value="false"/> + <boolean name="aps_compliant" value="false"/> <string name="privacy_policy" value="www.example.com"/> - <string-array name="security_endpoint" num="3"> + <string-array name="first_party_endpoints" num="1"> <item value="url1"/> - <item value="url2"/> - <item value="url3"/> </string-array> - <string-array name="first_party_endpoint" num="1"> - <item value="url1"/> - </string-array> - <string-array name="service_provider_endpoint" num="2"> + <string-array name="service_provider_endpoints" num="2"> <item value="url55"/> <item value="url56"/> </string-array> - <string name="category" value="Food and drink"/> - <string name="email" value="max@maxloh.com"/> </pbundle_as_map> </pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml index b2ff4495a6d2..cadf213c3e2e 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml @@ -1,5 +1,5 @@ <app-metadata-bundles version="123"> - <safety-labels version="12345"> + <safety-labels> <data-labels> <data-shared dataCategory="contacts" dataType="contacts" diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml index 81277bf456a4..7aafd2346846 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml @@ -1,7 +1,6 @@ <bundle> <long name="version" value="123"/> <pbundle_as_map name="safety_labels"> - <long name="version" value="12345"/> <pbundle_as_map name="data_labels"> <pbundle_as_map name="data_shared"> <pbundle_as_map name="contacts"> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml index 41b32b5ed7b0..592307937896 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml @@ -1,5 +1,5 @@ <app-metadata-bundles version="123"> - <safety-labels version="12345"> + <safety-labels> <data-labels> <data-shared dataType="location_data_type_approx_location" @@ -10,24 +10,18 @@ isSharingOptional="true" purposes="app_functionality|analytics" /> </data-labels> - <security-labels - isDataDeletable="true" - isDataEncrypted="false" - /> - <third-party-verification url="www.example.com"> - </third-party-verification> </safety-labels> <system-app-safety-label declaration="true"> </system-app-safety-label> <transparency-info> - <developer-info - name="max" - email="max@example.com" - address="111 blah lane" - countryRegion="US" - relationship="aosp" - website="example.com" - registryId="registry_id" /> - <app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" /> + <app-info apsCompliant="false" privacyPolicy="www.example.com"> + <first-party-endpoints> + <item>url1</item> + </first-party-endpoints> + <service-provider-endpoints> + <item>url55</item> + <item>url56</item> + </service-provider-endpoints> + </app-info> </transparency-info> </app-metadata-bundles>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml index c11ac4359ecd..c24087e483b6 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml @@ -1,7 +1,6 @@ <bundle> <long name="version" value="123"/> <pbundle_as_map name="safety_labels"> - <long name="version" value="12345"/> <pbundle_as_map name="data_labels"> <pbundle_as_map name="data_shared"> <pbundle_as_map name="location"> @@ -21,49 +20,21 @@ </pbundle_as_map> </pbundle_as_map> </pbundle_as_map> - <pbundle_as_map name="security_labels"> - <boolean name="is_data_deletable" value="true"/> - <boolean name="is_data_encrypted" value="false"/> - </pbundle_as_map> - <pbundle_as_map name="third_party_verification"> - <string name="url" value="www.example.com"/> - </pbundle_as_map> </pbundle_as_map> <pbundle_as_map name="system_app_safety_label"> <boolean name="declaration" value="true"/> </pbundle_as_map> <pbundle_as_map name="transparency_info"> - <pbundle_as_map name="developer_info"> - <string name="name" value="max"/> - <string name="email" value="max@example.com"/> - <string name="address" value="111 blah lane"/> - <string name="country_region" value="US"/> - <long name="relationship" value="5"/> - <string name="website" value="example.com"/> - <string name="app_developer_registry_id" value="registry_id"/> - </pbundle_as_map> <pbundle_as_map name="app_info"> - <string name="title" value="beervision"/> - <string name="description" value="a beer app"/> - <boolean name="contains_ads" value="true"/> - <boolean name="obey_aps" value="false"/> - <boolean name="ads_fingerprinting" value="false"/> - <boolean name="security_fingerprinting" value="false"/> + <boolean name="aps_compliant" value="false"/> <string name="privacy_policy" value="www.example.com"/> - <string-array name="security_endpoint" num="3"> - <item value="url1"/> - <item value="url2"/> - <item value="url3"/> - </string-array> - <string-array name="first_party_endpoint" num="1"> + <string-array name="first_party_endpoints" num="1"> <item value="url1"/> </string-array> - <string-array name="service_provider_endpoint" num="2"> + <string-array name="service_provider_endpoints" num="2"> <item value="url55"/> <item value="url56"/> </string-array> - <string name="category" value="Food and drink"/> - <string name="email" value="max@maxloh.com"/> </pbundle_as_map> </pbundle_as_map> </bundle>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml index ac844b3b2767..a4242d06fb15 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml @@ -1,5 +1,5 @@ <app-metadata-bundles version="123"> - <safety-labels version="12345"> + <safety-labels> <data-labels> <data-shared dataCategory="location" dataType="precise_location" diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml index d0a3bfa7e64f..79afaf7d70d5 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml @@ -1,7 +1,6 @@ <bundle> <long name="version" value="123"/> <pbundle_as_map name="safety_labels"> - <long name="version" value="12345"/> <pbundle_as_map name="data_labels"> <pbundle_as_map name="data_shared"> <pbundle_as_map name="location"> diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index 624a1987638e..5c6469706e18 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -41,6 +41,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, + FeatureAutomotiveDetector.ISSUE, ) override val api: Int diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/FeatureAutomotiveDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/FeatureAutomotiveDetector.kt new file mode 100644 index 000000000000..972b0c98eb94 --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/FeatureAutomotiveDetector.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.android.lint + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.ConstantEvaluator +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiMethod +import java.util.EnumSet +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.ULiteralExpression +import org.jetbrains.uast.UReferenceExpression + +/** + * A detector to check the usage of PackageManager.hasSystemFeature(" + * android.hardware.type.automotive") in CTS tests. + */ +class FeatureAutomotiveDetector : Detector(), SourceCodeScanner { + + companion object { + + val EXPLANATION = + """ + This class uses PackageManager.hasSystemFeature(\"android.hardware.type.automotive\") \ + or other equivalent methods. \ + If it is used to make a CTS test behave differently on AAOS, you should use \ + @RequireAutomotive or @RequireNotAutomotive instead; otherwise, please ignore this \ + warning. See https://g3doc.corp.google.com/wireless/android/partner/compatibility/\ + g3doc/dev/write-a-test/index.md#write-a-test-that-behaves-differently-on-aaos + """ + + val ISSUE: Issue = + Issue.create( + id = "UsingFeatureAutomotiveInCTS", + briefDescription = + "PackageManager.hasSystemFeature(\"" + + " android.hardware.type.automotive\") is used in CTS tests", + explanation = EXPLANATION, + category = Category.TESTING, + priority = 8, + severity = Severity.WARNING, + implementation = + Implementation( + FeatureAutomotiveDetector::class.java, + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) + ) + ) + } + + override fun getApplicableMethodNames() = + listOf("hasSystemFeature", "hasFeature", "hasDeviceFeature", "bypassTestForFeatures") + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + node.valueArguments.forEach { + val value = + when (it) { + is ULiteralExpression -> it.value + is UReferenceExpression -> ConstantEvaluator.evaluate(context, it) + else -> null + } + if (value is String && value == "android.hardware.type.automotive") { + context.report( + issue = ISSUE, + location = context.getNameLocation(method), + message = EXPLANATION + ) + } + } + } +} diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/FeatureAutomotiveDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/FeatureAutomotiveDetectorTest.kt new file mode 100644 index 000000000000..b5e2c00965f5 --- /dev/null +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/FeatureAutomotiveDetectorTest.kt @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.android.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +@Suppress("UnstableApiUsage") +class FeatureAutomotiveDetectorTest : LintDetectorTest() { + val explanation = + FeatureAutomotiveDetector.EXPLANATION.replace("\\", "").replace("\n ", "") + + " [UsingFeatureAutomotiveInCTS]" + + override fun getDetector(): Detector = FeatureAutomotiveDetector() + override fun getIssues(): List<Issue> = listOf(FeatureAutomotiveDetector.ISSUE) + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + @Test + fun testWarning1() { + lint() + .files( + java( + """ + import android.content.pm.PackageManager; + + public class Foo { + + private void fun() { + PackageManager.getInstance().hasSystemFeature( + "android.hardware.type.automotive"); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expect( + """ + src/android/content/pm/PackageManager.java:13: Warning: $explanation + public boolean hasSystemFeature(String feature) { + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWarning2() { + lint() + .files( + java( + """ + import android.content.pm.PackageManager; + + public class Foo { + + private void fun() { + String featureName = "android.hardware.type.automotive"; + PackageManager.getInstance().hasSystemFeature(featureName); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expect( + """ + src/android/content/pm/PackageManager.java:13: Warning: $explanation + public boolean hasSystemFeature(String feature) { + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWarning3() { + lint() + .files( + java( + """ + import android.content.pm.PackageManager; + + public class Foo { + + private void fun() { + PackageManager.getInstance().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expect( + """ + src/android/content/pm/PackageManager.java:13: Warning: $explanation + public boolean hasSystemFeature(String feature) { + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWarning4() { + lint() + .files( + java( + """ + import android.content.pm.PackageManager; + + public class Foo { + + private void fun() { + String featureName = PackageManager.FEATURE_AUTOMOTIVE; + PackageManager.getInstance().hasSystemFeature(featureName); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expect( + """ + src/android/content/pm/PackageManager.java:13: Warning: $explanation + public boolean hasSystemFeature(String feature) { + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWarning5() { + lint() + .files( + java( + """ + import com.android.example.Utils; + + public class Foo { + + private void fun() { + Utils.hasFeature("android.hardware.type.automotive"); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expect( + """ + src/com/android/example/Utils.java:7: Warning: $explanation + public static boolean hasFeature(String feature) { + ~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWarning6() { + lint() + .files( + java( + """ + import com.android.example.Utils; + + public class Foo { + + private void fun() { + Utils.hasDeviceFeature("android.hardware.type.automotive"); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expect( + """ + src/com/android/example/Utils.java:11: Warning: $explanation + public static boolean hasDeviceFeature(String feature) { + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWarning7() { + lint() + .files( + java( + """ + import com.android.example.Utils; + + public class Foo { + + private void fun() { + Utils.hasFeature(new Object(), "android.hardware.type.automotive"); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expect( + """ + src/com/android/example/Utils.java:15: Warning: $explanation + public static boolean hasFeature(Object object, String feature) { + ~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWarning8() { + lint() + .files( + java( + """ + import com.android.example.Utils; + + public class Foo { + + private void fun() { + Utils.bypassTestForFeatures("android.hardware.type.automotive"); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expect( + """ + src/com/android/example/Utils.java:19: Warning: $explanation + public static boolean bypassTestForFeatures(String feature) { + ~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testNoWarning() { + lint() + .files( + java( + """ + import android.content.pm.PackageManager; + + public class Foo { + private void fun() { + String featureName1 = "android.hardware.type.automotive"; + String featureName2 = PackageManager.FEATURE_AUTOMOTIVE; + String notFeatureName = "FEATURE_AUTOMOTIVE"; + PackageManager.getInstance().hasSystemFeature(notFeatureName); + /* + PackageManager.getInstance().hasSystemFeature( + "android.hardware.type.automotive"); + */ + } + } + """ + ) + .indented(), + *stubs + ) + .issues(FeatureAutomotiveDetector.ISSUE) + .run() + .expectClean() + } + + private val pmStub: TestFile = + java( + """ + package android.content.pm; + + import java.lang.String; + + public class PackageManager { + public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; + + public static PackageManager getInstance() { + return new PackageManager(); + } + + public boolean hasSystemFeature(String feature) { + return true; + } + } + """ + ) + + private val exampleStub: TestFile = + java( + """ + package com.android.example; + + import java.lang.String; + + public class Utils { + public static boolean hasFeature(String feature) { + return true; + } + + public static boolean hasDeviceFeature(String feature) { + return true; + } + + public static boolean hasFeature(Object object, String feature) { + return true; + } + + public static boolean bypassTestForFeatures(String feature) { + return true; + } + } + """ + ) + + private val stubs = arrayOf(pmStub, exampleStub) +} |