diff options
517 files changed, 13381 insertions, 6022 deletions
diff --git a/Android.bp b/Android.bp index cff863b44499..64d2c66f013c 100644 --- a/Android.bp +++ b/Android.bp @@ -410,6 +410,7 @@ java_defaults { "spatializer-aidl-java", "audiopolicy-aidl-java", "sounddose-aidl-java", + "modules-utils-expresslog", ], } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index aef9dd058658..3aec8ba39a35 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1588,12 +1588,12 @@ public class JobSchedulerService extends com.android.server.SystemService final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>(); synchronized (mLock) { ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid); - // Write out for loop to avoid addAll() creating an Iterator. + // Write out for loop to avoid creating an Iterator. for (int i = jobs.size() - 1; i >= 0; i--) { final JobStatus job = jobs.valueAt(i); List<JobInfo> outList = outMap.get(job.getNamespace()); if (outList == null) { - outList = new ArrayList<JobInfo>(jobs.size()); + outList = new ArrayList<>(); outMap.put(job.getNamespace(), outList); } @@ -1606,7 +1606,7 @@ public class JobSchedulerService extends com.android.server.SystemService private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) { synchronized (mLock) { ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid); - ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size()); + ArrayList<JobInfo> outList = new ArrayList<>(); // Write out for loop to avoid addAll() creating an Iterator. for (int i = jobs.size() - 1; i >= 0; i--) { final JobStatus job = jobs.valueAt(i); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java index fc6022859f5f..ba62e96b2a32 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java @@ -31,7 +31,7 @@ import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.expresslog.Counter; +import com.android.modules.expresslog.Counter; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt index 996c3882701d..aebace57aa2c 100644 --- a/boot/boot-image-profile.txt +++ b/boot/boot-image-profile.txt @@ -33583,8 +33583,8 @@ Lcom/android/internal/dynamicanimation/animation/DynamicAnimation; Lcom/android/internal/dynamicanimation/animation/Force; Lcom/android/internal/dynamicanimation/animation/SpringAnimation; Lcom/android/internal/dynamicanimation/animation/SpringForce; -Lcom/android/internal/expresslog/Counter; -Lcom/android/internal/expresslog/Utils; +Lcom/android/modules/expresslog/Counter; +Lcom/android/modules/expresslog/Utils; Lcom/android/internal/graphics/ColorUtils$ContrastCalculator; Lcom/android/internal/graphics/ColorUtils; Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider; diff --git a/boot/preloaded-classes b/boot/preloaded-classes index 21ae13474d84..4293caf57aea 100644 --- a/boot/preloaded-classes +++ b/boot/preloaded-classes @@ -10784,8 +10784,8 @@ com.android.internal.dynamicanimation.animation.DynamicAnimation com.android.internal.dynamicanimation.animation.Force com.android.internal.dynamicanimation.animation.SpringAnimation com.android.internal.dynamicanimation.animation.SpringForce -com.android.internal.expresslog.Counter -com.android.internal.expresslog.Utils +com.android.modules.expresslog.Counter +com.android.modules.expresslog.Utils com.android.internal.graphics.ColorUtils$ContrastCalculator com.android.internal.graphics.ColorUtils com.android.internal.graphics.SfVsyncFrameCallbackProvider diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index c4e8b0e49af3..a8b6c0b70804 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -105,6 +105,7 @@ static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress"; static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays"; static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled"; static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1; +static const int MAX_CHECK_EXIT_INTERVAL_US = 50000; static constexpr size_t TEXT_POS_LEN_MAX = 16; static const int DYNAMIC_COLOR_COUNT = 4; static const char U_TEXTURE[] = "uTexture"; @@ -1678,7 +1679,17 @@ bool BootAnimation::playAnimation(const Animation& animation) { checkExit(); } - usleep(part.pause * ns2us(frameDuration)); + int pauseDuration = part.pause * ns2us(frameDuration); + while(pauseDuration > 0 && !exitPending()){ + if (pauseDuration > MAX_CHECK_EXIT_INTERVAL_US) { + usleep(MAX_CHECK_EXIT_INTERVAL_US); + pauseDuration -= MAX_CHECK_EXIT_INTERVAL_US; + } else { + usleep(pauseDuration); + break; + } + checkExit(); + } if (exitPending() && !part.count && mCurrentInset >= mTargetInset && !part.hasFadingPhase()) { diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt index 3cc990873c87..bb0748764cc8 100644 --- a/config/boot-image-profile.txt +++ b/config/boot-image-profile.txt @@ -43717,8 +43717,8 @@ Lcom/android/internal/dynamicanimation/animation/DynamicAnimation; Lcom/android/internal/dynamicanimation/animation/Force; Lcom/android/internal/dynamicanimation/animation/SpringAnimation; Lcom/android/internal/dynamicanimation/animation/SpringForce; -Lcom/android/internal/expresslog/Counter; -Lcom/android/internal/expresslog/Utils; +Lcom/android/modules/expresslog/Counter; +Lcom/android/modules/expresslog/Utils; Lcom/android/internal/graphics/ColorUtils$ContrastCalculator; Lcom/android/internal/graphics/ColorUtils; Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider; diff --git a/config/preloaded-classes b/config/preloaded-classes index 8e50fe8e4e0f..1812c2bb61d6 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -10815,8 +10815,8 @@ com.android.internal.dynamicanimation.animation.DynamicAnimation com.android.internal.dynamicanimation.animation.Force com.android.internal.dynamicanimation.animation.SpringAnimation com.android.internal.dynamicanimation.animation.SpringForce -com.android.internal.expresslog.Counter -com.android.internal.expresslog.Utils +com.android.modules.expresslog.Counter +com.android.modules.expresslog.Utils com.android.internal.graphics.ColorUtils$ContrastCalculator com.android.internal.graphics.ColorUtils com.android.internal.graphics.SfVsyncFrameCallbackProvider diff --git a/core/api/current.txt b/core/api/current.txt index 80abd84733d7..288ab479c0fb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -377,7 +377,7 @@ package android { public static final class R.attr { ctor public R.attr(); field public static final int absListViewStyle = 16842858; // 0x101006a - field public static final int accessibilityDataSensitive; + field public static final int accessibilityDataSensitive = 16844407; // 0x1010677 field public static final int accessibilityEventTypes = 16843648; // 0x1010380 field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 field public static final int accessibilityFlags = 16843652; // 0x1010384 @@ -445,12 +445,12 @@ package android { field public static final int allowGameFpsOverride = 16844378; // 0x101065a field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612 field public static final int allowParallelSyncs = 16843570; // 0x1010332 - field public static final int allowSharedIsolatedProcess; + field public static final int allowSharedIsolatedProcess = 16844413; // 0x101067d field public static final int allowSingleTap = 16843353; // 0x1010259 field public static final int allowTaskReparenting = 16843268; // 0x1010204 field public static final int allowUndo = 16843999; // 0x10104df field public static final int allowUntrustedActivityEmbedding = 16844393; // 0x1010669 - field public static final int allowUpdateOwnership; + field public static final int allowUpdateOwnership = 16844416; // 0x1010680 field public static final int alpha = 16843551; // 0x101031f field public static final int alphabeticModifiers = 16844110; // 0x101054e field public static final int alphabeticShortcut = 16843235; // 0x10101e3 @@ -556,7 +556,7 @@ package android { field public static final int canTakeScreenshot = 16844303; // 0x101060f field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final int cantSaveState = 16844142; // 0x101056e - field public static final int capability; + field public static final int capability = 16844423; // 0x1010687 field @Deprecated public static final int capitalize = 16843113; // 0x1010169 field public static final int category = 16843752; // 0x10103e8 field public static final int centerBright = 16842956; // 0x10100cc @@ -741,7 +741,7 @@ package android { field public static final int ellipsize = 16842923; // 0x10100ab field public static final int ems = 16843096; // 0x1010158 field public static final int enableOnBackInvokedCallback = 16844396; // 0x101066c - field public static final int enableTextStylingShortcuts; + field public static final int enableTextStylingShortcuts = 16844408; // 0x1010678 field public static final int enableVrMode = 16844069; // 0x1010525 field public static final int enabled = 16842766; // 0x101000e field public static final int end = 16843996; // 0x10104dc @@ -810,7 +810,7 @@ package android { field public static final int focusableInTouchMode = 16842971; // 0x10100db field public static final int focusedByDefault = 16844100; // 0x1010544 field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343 - field public static final int focusedSearchResultHighlightColor; + field public static final int focusedSearchResultHighlightColor = 16844419; // 0x1010683 field public static final int font = 16844082; // 0x1010532 field public static final int fontFamily = 16843692; // 0x10103ac field public static final int fontFeatureSettings = 16843959; // 0x10104b7 @@ -896,10 +896,10 @@ package android { field public static final int hand_secondTintMode = 16844349; // 0x101063d field public static final int handle = 16843354; // 0x101025a field public static final int handleProfiling = 16842786; // 0x1010022 - field public static final int handwritingBoundsOffsetBottom; - field public static final int handwritingBoundsOffsetLeft; - field public static final int handwritingBoundsOffsetRight; - field public static final int handwritingBoundsOffsetTop; + field public static final int handwritingBoundsOffsetBottom = 16844406; // 0x1010676 + field public static final int handwritingBoundsOffsetLeft = 16844403; // 0x1010673 + field public static final int handwritingBoundsOffsetRight = 16844405; // 0x1010675 + field public static final int handwritingBoundsOffsetTop = 16844404; // 0x1010674 field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e field public static final int hardwareAccelerated = 16843475; // 0x10102d3 field public static final int hasCode = 16842764; // 0x101000c @@ -986,7 +986,7 @@ package android { field public static final int isAlwaysSyncable = 16843571; // 0x1010333 field public static final int isAsciiCapable = 16843753; // 0x10103e9 field public static final int isAuxiliary = 16843647; // 0x101037f - field public static final int isCredential; + field public static final int isCredential = 16844417; // 0x1010681 field public static final int isDefault = 16843297; // 0x1010221 field public static final int isFeatureSplit = 16844123; // 0x101055b field public static final int isGame = 16843764; // 0x10103f4 @@ -1021,8 +1021,8 @@ package android { field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234 field @Deprecated public static final int keyWidth = 16843325; // 0x101023d field public static final int keyboardLayout = 16843691; // 0x10103ab - field public static final int keyboardLayoutType; - field public static final int keyboardLocale; + field public static final int keyboardLayoutType = 16844415; // 0x101067f + field public static final int keyboardLocale = 16844414; // 0x101067e field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 field public static final int keycode = 16842949; // 0x10100c5 @@ -1263,8 +1263,8 @@ package android { field public static final int persistentDrawingCache = 16842990; // 0x10100ee field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563 field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167 - field public static final int physicalKeyboardHintLanguageTag; - field public static final int physicalKeyboardHintLayoutType; + field public static final int physicalKeyboardHintLanguageTag = 16844411; // 0x101067b + field public static final int physicalKeyboardHintLayoutType = 16844412; // 0x101067c field public static final int pivotX = 16843189; // 0x10101b5 field public static final int pivotY = 16843190; // 0x10101b6 field public static final int pointerIcon = 16844041; // 0x1010509 @@ -1354,7 +1354,7 @@ package android { field public static final int requireDeviceUnlock = 16843756; // 0x10103ec field public static final int required = 16843406; // 0x101028e field public static final int requiredAccountType = 16843734; // 0x10103d6 - field public static final int requiredDisplayCategory; + field public static final int requiredDisplayCategory = 16844409; // 0x1010679 field public static final int requiredFeature = 16844116; // 0x1010554 field public static final int requiredForAllUsers = 16843728; // 0x10103d0 field public static final int requiredNotFeature = 16844117; // 0x1010555 @@ -1422,7 +1422,7 @@ package android { field public static final int searchHintIcon = 16843988; // 0x10104d4 field public static final int searchIcon = 16843907; // 0x1010483 field public static final int searchMode = 16843221; // 0x10101d5 - field public static final int searchResultHighlightColor; + field public static final int searchResultHighlightColor = 16844418; // 0x1010682 field public static final int searchSettingsDescription = 16843402; // 0x101028a field public static final int searchSuggestAuthority = 16843222; // 0x10101d6 field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9 @@ -1449,7 +1449,7 @@ package android { field public static final int sessionService = 16843837; // 0x101043d field public static final int settingsActivity = 16843301; // 0x1010225 field public static final int settingsSliceUri = 16844179; // 0x1010593 - field public static final int settingsSubtitle; + field public static final int settingsSubtitle = 16844422; // 0x1010686 field public static final int setupActivity = 16843766; // 0x10103f6 field public static final int shadowColor = 16843105; // 0x1010161 field public static final int shadowDx = 16843106; // 0x1010162 @@ -1555,7 +1555,7 @@ package android { field public static final int strokeLineJoin = 16843788; // 0x101040c field public static final int strokeMiterLimit = 16843789; // 0x101040d field public static final int strokeWidth = 16843783; // 0x1010407 - field public static final int stylusHandwritingSettingsActivity; + field public static final int stylusHandwritingSettingsActivity = 16844420; // 0x1010684 field public static final int subMenuArrow = 16844019; // 0x10104f3 field public static final int submitBackground = 16843912; // 0x1010488 field public static final int subtitle = 16843473; // 0x10102d1 @@ -1861,7 +1861,7 @@ package android { field public static final int windowMinWidthMajor = 16843606; // 0x1010356 field public static final int windowMinWidthMinor = 16843607; // 0x1010357 field public static final int windowNoDisplay = 16843294; // 0x101021e - field public static final int windowNoMoveAnimation; + field public static final int windowNoMoveAnimation = 16844421; // 0x1010685 field public static final int windowNoTitle = 16842838; // 0x1010056 field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf field public static final int windowReenterTransition = 16843951; // 0x10104af @@ -1966,18 +1966,18 @@ package android { field public static final int system_accent3_700 = 17170522; // 0x106005a field public static final int system_accent3_800 = 17170523; // 0x106005b field public static final int system_accent3_900 = 17170524; // 0x106005c - field public static final int system_background_dark; - field public static final int system_background_light; - field public static final int system_control_activated_dark; - field public static final int system_control_activated_light; - field public static final int system_control_highlight_dark; - field public static final int system_control_highlight_light; - field public static final int system_control_normal_dark; - field public static final int system_control_normal_light; - field public static final int system_error_container_dark; - field public static final int system_error_container_light; - field public static final int system_error_dark; - field public static final int system_error_light; + field public static final int system_background_dark = 17170581; // 0x1060095 + field public static final int system_background_light = 17170538; // 0x106006a + field public static final int system_control_activated_dark = 17170599; // 0x10600a7 + field public static final int system_control_activated_light = 17170556; // 0x106007c + field public static final int system_control_highlight_dark = 17170601; // 0x10600a9 + field public static final int system_control_highlight_light = 17170558; // 0x106007e + field public static final int system_control_normal_dark = 17170600; // 0x10600a8 + field public static final int system_control_normal_light = 17170557; // 0x106007d + field public static final int system_error_container_dark = 17170597; // 0x10600a5 + field public static final int system_error_container_light = 17170554; // 0x106007a + field public static final int system_error_dark = 17170595; // 0x10600a3 + field public static final int system_error_light = 17170552; // 0x1060078 field public static final int system_neutral1_0 = 17170461; // 0x106001d field public static final int system_neutral1_10 = 17170462; // 0x106001e field public static final int system_neutral1_100 = 17170464; // 0x1060020 @@ -2004,94 +2004,94 @@ package android { field public static final int system_neutral2_700 = 17170483; // 0x1060033 field public static final int system_neutral2_800 = 17170484; // 0x1060034 field public static final int system_neutral2_900 = 17170485; // 0x1060035 - field public static final int system_on_background_dark; - field public static final int system_on_background_light; - field public static final int system_on_error_container_dark; - field public static final int system_on_error_container_light; - field public static final int system_on_error_dark; - field public static final int system_on_error_light; - field public static final int system_on_primary_container_dark; - field public static final int system_on_primary_container_light; - field public static final int system_on_primary_dark; - field public static final int system_on_primary_fixed; - field public static final int system_on_primary_fixed_variant; - field public static final int system_on_primary_light; - field public static final int system_on_secondary_container_dark; - field public static final int system_on_secondary_container_light; - field public static final int system_on_secondary_dark; - field public static final int system_on_secondary_fixed; - field public static final int system_on_secondary_fixed_variant; - field public static final int system_on_secondary_light; - field public static final int system_on_surface_dark; - field public static final int system_on_surface_light; - field public static final int system_on_surface_variant_dark; - field public static final int system_on_surface_variant_light; - field public static final int system_on_tertiary_container_dark; - field public static final int system_on_tertiary_container_light; - field public static final int system_on_tertiary_dark; - field public static final int system_on_tertiary_fixed; - field public static final int system_on_tertiary_fixed_variant; - field public static final int system_on_tertiary_light; - field public static final int system_outline_dark; - field public static final int system_outline_light; - field public static final int system_outline_variant_dark; - field public static final int system_outline_variant_light; - field public static final int system_palette_key_color_neutral_dark; - field public static final int system_palette_key_color_neutral_light; - field public static final int system_palette_key_color_neutral_variant_dark; - field public static final int system_palette_key_color_neutral_variant_light; - field public static final int system_palette_key_color_primary_dark; - field public static final int system_palette_key_color_primary_light; - field public static final int system_palette_key_color_secondary_dark; - field public static final int system_palette_key_color_secondary_light; - field public static final int system_palette_key_color_tertiary_dark; - field public static final int system_palette_key_color_tertiary_light; - field public static final int system_primary_container_dark; - field public static final int system_primary_container_light; - field public static final int system_primary_dark; - field public static final int system_primary_fixed; - field public static final int system_primary_fixed_dim; - field public static final int system_primary_light; - field public static final int system_secondary_container_dark; - field public static final int system_secondary_container_light; - field public static final int system_secondary_dark; - field public static final int system_secondary_fixed; - field public static final int system_secondary_fixed_dim; - field public static final int system_secondary_light; - field public static final int system_surface_bright_dark; - field public static final int system_surface_bright_light; - field public static final int system_surface_container_dark; - field public static final int system_surface_container_high_dark; - field public static final int system_surface_container_high_light; - field public static final int system_surface_container_highest_dark; - field public static final int system_surface_container_highest_light; - field public static final int system_surface_container_light; - field public static final int system_surface_container_low_dark; - field public static final int system_surface_container_low_light; - field public static final int system_surface_container_lowest_dark; - field public static final int system_surface_container_lowest_light; - field public static final int system_surface_dark; - field public static final int system_surface_dim_dark; - field public static final int system_surface_dim_light; - field public static final int system_surface_light; - field public static final int system_surface_variant_dark; - field public static final int system_surface_variant_light; - field public static final int system_tertiary_container_dark; - field public static final int system_tertiary_container_light; - field public static final int system_tertiary_dark; - field public static final int system_tertiary_fixed; - field public static final int system_tertiary_fixed_dim; - field public static final int system_tertiary_light; - field public static final int system_text_hint_inverse_dark; - field public static final int system_text_hint_inverse_light; - field public static final int system_text_primary_inverse_dark; - field public static final int system_text_primary_inverse_disable_only_dark; - field public static final int system_text_primary_inverse_disable_only_light; - field public static final int system_text_primary_inverse_light; - field public static final int system_text_secondary_and_tertiary_inverse_dark; - field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark; - field public static final int system_text_secondary_and_tertiary_inverse_disabled_light; - field public static final int system_text_secondary_and_tertiary_inverse_light; + field public static final int system_on_background_dark = 17170582; // 0x1060096 + field public static final int system_on_background_light = 17170539; // 0x106006b + field public static final int system_on_error_container_dark = 17170598; // 0x10600a6 + field public static final int system_on_error_container_light = 17170555; // 0x106007b + field public static final int system_on_error_dark = 17170596; // 0x10600a4 + field public static final int system_on_error_light = 17170553; // 0x1060079 + field public static final int system_on_primary_container_dark = 17170570; // 0x106008a + field public static final int system_on_primary_container_light = 17170527; // 0x106005f + field public static final int system_on_primary_dark = 17170572; // 0x106008c + field public static final int system_on_primary_fixed = 17170614; // 0x10600b6 + field public static final int system_on_primary_fixed_variant = 17170615; // 0x10600b7 + field public static final int system_on_primary_light = 17170529; // 0x1060061 + field public static final int system_on_secondary_container_dark = 17170574; // 0x106008e + field public static final int system_on_secondary_container_light = 17170531; // 0x1060063 + field public static final int system_on_secondary_dark = 17170576; // 0x1060090 + field public static final int system_on_secondary_fixed = 17170618; // 0x10600ba + field public static final int system_on_secondary_fixed_variant = 17170619; // 0x10600bb + field public static final int system_on_secondary_light = 17170533; // 0x1060065 + field public static final int system_on_surface_dark = 17170584; // 0x1060098 + field public static final int system_on_surface_light = 17170541; // 0x106006d + field public static final int system_on_surface_variant_dark = 17170593; // 0x10600a1 + field public static final int system_on_surface_variant_light = 17170550; // 0x1060076 + field public static final int system_on_tertiary_container_dark = 17170578; // 0x1060092 + field public static final int system_on_tertiary_container_light = 17170535; // 0x1060067 + field public static final int system_on_tertiary_dark = 17170580; // 0x1060094 + field public static final int system_on_tertiary_fixed = 17170622; // 0x10600be + field public static final int system_on_tertiary_fixed_variant = 17170623; // 0x10600bf + field public static final int system_on_tertiary_light = 17170537; // 0x1060069 + field public static final int system_outline_dark = 17170594; // 0x10600a2 + field public static final int system_outline_light = 17170551; // 0x1060077 + field public static final int system_outline_variant_dark = 17170625; // 0x10600c1 + field public static final int system_outline_variant_light = 17170624; // 0x10600c0 + field public static final int system_palette_key_color_neutral_dark = 17170610; // 0x10600b2 + field public static final int system_palette_key_color_neutral_light = 17170567; // 0x1060087 + field public static final int system_palette_key_color_neutral_variant_dark = 17170611; // 0x10600b3 + field public static final int system_palette_key_color_neutral_variant_light = 17170568; // 0x1060088 + field public static final int system_palette_key_color_primary_dark = 17170607; // 0x10600af + field public static final int system_palette_key_color_primary_light = 17170564; // 0x1060084 + field public static final int system_palette_key_color_secondary_dark = 17170608; // 0x10600b0 + field public static final int system_palette_key_color_secondary_light = 17170565; // 0x1060085 + field public static final int system_palette_key_color_tertiary_dark = 17170609; // 0x10600b1 + field public static final int system_palette_key_color_tertiary_light = 17170566; // 0x1060086 + field public static final int system_primary_container_dark = 17170569; // 0x1060089 + field public static final int system_primary_container_light = 17170526; // 0x106005e + field public static final int system_primary_dark = 17170571; // 0x106008b + field public static final int system_primary_fixed = 17170612; // 0x10600b4 + field public static final int system_primary_fixed_dim = 17170613; // 0x10600b5 + field public static final int system_primary_light = 17170528; // 0x1060060 + field public static final int system_secondary_container_dark = 17170573; // 0x106008d + field public static final int system_secondary_container_light = 17170530; // 0x1060062 + field public static final int system_secondary_dark = 17170575; // 0x106008f + field public static final int system_secondary_fixed = 17170616; // 0x10600b8 + field public static final int system_secondary_fixed_dim = 17170617; // 0x10600b9 + field public static final int system_secondary_light = 17170532; // 0x1060064 + field public static final int system_surface_bright_dark = 17170590; // 0x106009e + field public static final int system_surface_bright_light = 17170547; // 0x1060073 + field public static final int system_surface_container_dark = 17170587; // 0x106009b + field public static final int system_surface_container_high_dark = 17170588; // 0x106009c + field public static final int system_surface_container_high_light = 17170545; // 0x1060071 + field public static final int system_surface_container_highest_dark = 17170589; // 0x106009d + field public static final int system_surface_container_highest_light = 17170546; // 0x1060072 + field public static final int system_surface_container_light = 17170544; // 0x1060070 + field public static final int system_surface_container_low_dark = 17170585; // 0x1060099 + field public static final int system_surface_container_low_light = 17170542; // 0x106006e + field public static final int system_surface_container_lowest_dark = 17170586; // 0x106009a + field public static final int system_surface_container_lowest_light = 17170543; // 0x106006f + field public static final int system_surface_dark = 17170583; // 0x1060097 + field public static final int system_surface_dim_dark = 17170591; // 0x106009f + field public static final int system_surface_dim_light = 17170548; // 0x1060074 + field public static final int system_surface_light = 17170540; // 0x106006c + field public static final int system_surface_variant_dark = 17170592; // 0x10600a0 + field public static final int system_surface_variant_light = 17170549; // 0x1060075 + field public static final int system_tertiary_container_dark = 17170577; // 0x1060091 + field public static final int system_tertiary_container_light = 17170534; // 0x1060066 + field public static final int system_tertiary_dark = 17170579; // 0x1060093 + field public static final int system_tertiary_fixed = 17170620; // 0x10600bc + field public static final int system_tertiary_fixed_dim = 17170621; // 0x10600bd + field public static final int system_tertiary_light = 17170536; // 0x1060068 + field public static final int system_text_hint_inverse_dark = 17170606; // 0x10600ae + field public static final int system_text_hint_inverse_light = 17170563; // 0x1060083 + field public static final int system_text_primary_inverse_dark = 17170602; // 0x10600aa + field public static final int system_text_primary_inverse_disable_only_dark = 17170604; // 0x10600ac + field public static final int system_text_primary_inverse_disable_only_light = 17170561; // 0x1060081 + field public static final int system_text_primary_inverse_light = 17170559; // 0x106007f + field public static final int system_text_secondary_and_tertiary_inverse_dark = 17170603; // 0x10600ab + field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark = 17170605; // 0x10600ad + field public static final int system_text_secondary_and_tertiary_inverse_disabled_light = 17170562; // 0x1060082 + field public static final int system_text_secondary_and_tertiary_inverse_light = 17170560; // 0x1060080 field public static final int tab_indicator_text = 17170441; // 0x1060009 field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010 field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011 @@ -2310,7 +2310,7 @@ package android { field public static final int accessibilityActionPageUp = 16908358; // 0x1020046 field public static final int accessibilityActionPressAndHold = 16908362; // 0x102004a field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a - field public static final int accessibilityActionScrollInDirection; + field public static final int accessibilityActionScrollInDirection = 16908382; // 0x102005e field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039 field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037 @@ -2331,7 +2331,7 @@ package android { field public static final int addToDictionary = 16908330; // 0x102002a field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 - field public static final int bold; + field public static final int bold = 16908379; // 0x102005b field public static final int button1 = 16908313; // 0x1020019 field public static final int button2 = 16908314; // 0x102001a field public static final int button3 = 16908315; // 0x102001b @@ -2357,7 +2357,7 @@ package android { field public static final int inputExtractAccessories = 16908378; // 0x102005a field public static final int inputExtractAction = 16908377; // 0x1020059 field public static final int inputExtractEditText = 16908325; // 0x1020025 - field public static final int italic; + field public static final int italic = 16908380; // 0x102005c field @Deprecated public static final int keyboardView = 16908326; // 0x1020026 field public static final int list = 16908298; // 0x102000a field public static final int list_container = 16908351; // 0x102003f @@ -2389,7 +2389,7 @@ package android { field public static final int textAssist = 16908353; // 0x1020041 field public static final int title = 16908310; // 0x1020016 field public static final int toggle = 16908311; // 0x1020017 - field public static final int underline; + field public static final int underline = 16908381; // 0x102005d field public static final int undo = 16908338; // 0x1020032 field public static final int widget_frame = 16908312; // 0x1020018 } @@ -13662,7 +13662,6 @@ package android.credentials { } public final class CredentialOption implements android.os.Parcelable { - ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean); method public int describeContents(); method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders(); method @NonNull public android.os.Bundle getCandidateQueryData(); @@ -32653,7 +32652,7 @@ package android.os { field public static final int S = 31; // 0x1f field public static final int S_V2 = 32; // 0x20 field public static final int TIRAMISU = 33; // 0x21 - field public static final int UPSIDE_DOWN_CAKE = 10000; // 0x2710 + field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22 } public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fbc69e34a644..ace7d59c9a45 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -410,14 +410,14 @@ package android { field public static final int sdkVersion = 16844304; // 0x1010610 field public static final int supportsAmbientMode = 16844173; // 0x101058d field public static final int userRestriction = 16844164; // 0x1010584 - field public static final int visualQueryDetectionService; + field public static final int visualQueryDetectionService = 16844410; // 0x101067a } public static final class R.bool { - field public static final int config_enableDefaultNotes; - field public static final int config_enableDefaultNotesForWorkProfile; + field public static final int config_enableDefaultNotes = 17891338; // 0x111000a + field public static final int config_enableDefaultNotesForWorkProfile = 17891339; // 0x111000b field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008 - field public static final int config_safetyProtectionEnabled; + field public static final int config_safetyProtectionEnabled = 17891337; // 0x1110009 field public static final int config_sendPackageName = 17891328; // 0x1110000 field public static final int config_showDefaultAssistant = 17891329; // 0x1110001 field public static final int config_showDefaultEmergency = 17891330; // 0x1110002 @@ -430,7 +430,7 @@ package android { public static final class R.dimen { field public static final int config_restrictedIconSize = 17104903; // 0x1050007 - field public static final int config_viewConfigurationHandwritingGestureLineMargin; + field public static final int config_viewConfigurationHandwritingGestureLineMargin = 17104906; // 0x105000a } public static final class R.drawable { @@ -452,7 +452,7 @@ package android { field public static final int config_defaultCallRedirection = 17039397; // 0x1040025 field public static final int config_defaultCallScreening = 17039398; // 0x1040026 field public static final int config_defaultDialer = 17039395; // 0x1040023 - field public static final int config_defaultNotes; + field public static final int config_defaultNotes = 17039429; // 0x1040045 field public static final int config_defaultSms = 17039396; // 0x1040024 field public static final int config_devicePolicyManagement = 17039421; // 0x104003d field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f @@ -468,10 +468,10 @@ package android { field public static final int config_systemAutomotiveCalendarSyncManager = 17039423; // 0x104003f field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028 field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029 - field public static final int config_systemCallStreaming; + field public static final int config_systemCallStreaming = 17039431; // 0x1040047 field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039 field public static final int config_systemContacts = 17039403; // 0x104002b - field public static final int config_systemFinancedDeviceController; + field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046 field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035 field public static final int config_systemSettingsIntelligence = 17039426; // 0x1040042 @@ -483,7 +483,7 @@ package android { field public static final int config_systemUi = 17039418; // 0x104003a field public static final int config_systemUiIntelligence = 17039410; // 0x1040032 field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037 - field public static final int config_systemWearHealthService; + field public static final int config_systemWearHealthService = 17039428; // 0x1040044 field public static final int config_systemWellbeing = 17039408; // 0x1040030 field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f field public static final int safety_protection_display_text = 17039425; // 0x1040041 @@ -10117,7 +10117,7 @@ package android.net.wifi.sharedconnectivity.app { public final class NetworkProviderInfo implements android.os.Parcelable { method public int describeContents(); method @IntRange(from=0, to=100) public int getBatteryPercentage(); - method @IntRange(from=0, to=3) public int getConnectionStrength(); + method @IntRange(from=0, to=4) public int getConnectionStrength(); method @NonNull public String getDeviceName(); method public int getDeviceType(); method @NonNull public android.os.Bundle getExtras(); @@ -10136,7 +10136,7 @@ package android.net.wifi.sharedconnectivity.app { ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build(); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int); - method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int); + method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 9b5e31ac67be..5999e3c4a175 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -8,8 +8,6 @@ package android { field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; - field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE"; - field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; @@ -3429,8 +3427,14 @@ package android.view { method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier(); } + public abstract class InputEvent implements android.os.Parcelable { + method public abstract int getDisplayId(); + method public abstract void setDisplayId(int); + } + public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable { method public static String actionToString(int); + method public final int getDisplayId(); method public final void setDisplayId(int); field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800 field public static final int LAST_KEYCODE = 316; // 0x13c diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 0293bb53d3f0..95e446dde4da 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -343,7 +343,170 @@ public abstract class ActivityManagerInternal { */ public abstract boolean hasRunningActivity(int uid, @Nullable String packageName); - public abstract void updateOomAdj(); + /** + * Oom Adj Reason: none - internal use only, do not use it. + * @hide + */ + public static final int OOM_ADJ_REASON_NONE = 0; + + /** + * Oom Adj Reason: activity changes. + * @hide + */ + public static final int OOM_ADJ_REASON_ACTIVITY = 1; + + /** + * Oom Adj Reason: finishing a broadcast receiver. + * @hide + */ + public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2; + + /** + * Oom Adj Reason: starting a broadcast receiver. + * @hide + */ + public static final int OOM_ADJ_REASON_START_RECEIVER = 3; + + /** + * Oom Adj Reason: binding to a service. + * @hide + */ + public static final int OOM_ADJ_REASON_BIND_SERVICE = 4; + + /** + * Oom Adj Reason: unbinding from a service. + * @hide + */ + public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5; + + /** + * Oom Adj Reason: starting a service. + * @hide + */ + public static final int OOM_ADJ_REASON_START_SERVICE = 6; + + /** + * Oom Adj Reason: connecting to a content provider. + * @hide + */ + public static final int OOM_ADJ_REASON_GET_PROVIDER = 7; + + /** + * Oom Adj Reason: disconnecting from a content provider. + * @hide + */ + public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8; + + /** + * Oom Adj Reason: UI visibility changes. + * @hide + */ + public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9; + + /** + * Oom Adj Reason: device power allowlist changes. + * @hide + */ + public static final int OOM_ADJ_REASON_ALLOWLIST = 10; + + /** + * Oom Adj Reason: starting a process. + * @hide + */ + public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11; + + /** + * Oom Adj Reason: ending a process. + * @hide + */ + public static final int OOM_ADJ_REASON_PROCESS_END = 12; + + /** + * Oom Adj Reason: short FGS timeout. + * @hide + */ + public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13; + + /** + * Oom Adj Reason: system initialization. + * @hide + */ + public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14; + + /** + * Oom Adj Reason: backup/restore. + * @hide + */ + public static final int OOM_ADJ_REASON_BACKUP = 15; + + /** + * Oom Adj Reason: instrumented by the SHELL. + * @hide + */ + public static final int OOM_ADJ_REASON_SHELL = 16; + + /** + * Oom Adj Reason: task stack is being removed. + */ + public static final int OOM_ADJ_REASON_REMOVE_TASK = 17; + + /** + * Oom Adj Reason: uid idle. + */ + public static final int OOM_ADJ_REASON_UID_IDLE = 18; + + /** + * Oom Adj Reason: stop service. + */ + public static final int OOM_ADJ_REASON_STOP_SERVICE = 19; + + /** + * Oom Adj Reason: executing service. + */ + public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20; + + /** + * Oom Adj Reason: background restriction changes. + */ + public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21; + + /** + * Oom Adj Reason: A package or its component is disabled. + */ + public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22; + + @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = { + OOM_ADJ_REASON_NONE, + OOM_ADJ_REASON_ACTIVITY, + OOM_ADJ_REASON_FINISH_RECEIVER, + OOM_ADJ_REASON_START_RECEIVER, + OOM_ADJ_REASON_BIND_SERVICE, + OOM_ADJ_REASON_UNBIND_SERVICE, + OOM_ADJ_REASON_START_SERVICE, + OOM_ADJ_REASON_GET_PROVIDER, + OOM_ADJ_REASON_REMOVE_PROVIDER, + OOM_ADJ_REASON_UI_VISIBILITY, + OOM_ADJ_REASON_ALLOWLIST, + OOM_ADJ_REASON_PROCESS_BEGIN, + OOM_ADJ_REASON_PROCESS_END, + OOM_ADJ_REASON_SHORT_FGS_TIMEOUT, + OOM_ADJ_REASON_SYSTEM_INIT, + OOM_ADJ_REASON_BACKUP, + OOM_ADJ_REASON_SHELL, + OOM_ADJ_REASON_REMOVE_TASK, + OOM_ADJ_REASON_UID_IDLE, + OOM_ADJ_REASON_STOP_SERVICE, + OOM_ADJ_REASON_EXECUTING_SERVICE, + OOM_ADJ_REASON_RESTRICTION_CHANGE, + OOM_ADJ_REASON_COMPONENT_DISABLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface OomAdjReason {} + + /** + * Request to update oom adj. + */ + public abstract void updateOomAdj(@OomAdjReason int oomAdjReason); public abstract void updateCpuStats(); /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b48a8fb73832..3312294865d6 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1450,9 +1450,8 @@ public class AppOpsManager { public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD; - /** @hide Access to wrist temperature sensors. */ - public static final int OP_BODY_SENSORS_WRIST_TEMPERATURE = - AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE; + // App op deprecated/removed. + private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE; /** * Send an intent to launch instead of posting the notification to the status bar. @@ -1461,9 +1460,25 @@ public class AppOpsManager { */ public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT; + /** + * Hides camera indicator for sandboxed detection apps that directly access the service. + * + * @hide + */ + public static final int OP_CAMERA_SANDBOXED = + AppProtoEnums.APP_OP_CAMERA_SANDBOXED; + + /** + * Hides microphone indicator for sandboxed detection apps that directly access the service. + * + * @hide + */ + public static final int OP_RECORD_AUDIO_SANDBOXED = + AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 134; + public static final int _NUM_OP = 136; /** * All app ops represented as strings. @@ -1603,8 +1618,9 @@ public class AppOpsManager { OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION, OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, - OPSTR_BODY_SENSORS_WRIST_TEMPERATURE, OPSTR_USE_FULL_SCREEN_INTENT, + OPSTR_CAMERA_SANDBOXED, + OPSTR_RECORD_AUDIO_SANDBOXED }) public @interface AppOpString {} @@ -2013,6 +2029,20 @@ public class AppOpsManager { public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source"; /** + * Camera is being recorded in sandboxed detection process. + * + * @hide + */ + public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed"; + + /** + * Audio is being recorded in sandboxed detection process. + * + * @hide + */ + public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed"; + + /** * Allow apps to create the requests to manage the media files without user confirmation. * * @see android.Manifest.permission#MANAGE_MEDIA @@ -2189,11 +2219,10 @@ public class AppOpsManager { "android:capture_consentless_bugreport_on_userdebug_build"; /** - * Access to wrist temperature body sensors. + * App op deprecated/removed. * @hide */ - public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE = - "android:body_sensors_wrist_temperature"; + public static final String OPSTR_DEPRECATED_2 = "android:deprecated_2"; /** * Send an intent to launch instead of posting the notification to the status bar. @@ -2311,7 +2340,6 @@ public class AppOpsManager { OP_READ_MEDIA_VISUAL_USER_SELECTED, OP_FOREGROUND_SERVICE_SPECIAL_USE, OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, - OP_BODY_SENSORS_WRIST_TEMPERATURE, OP_USE_FULL_SCREEN_INTENT }; @@ -2731,14 +2759,15 @@ public class AppOpsManager { "CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD") .setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD) .build(), - new AppOpInfo.Builder(OP_BODY_SENSORS_WRIST_TEMPERATURE, - OPSTR_BODY_SENSORS_WRIST_TEMPERATURE, - "BODY_SENSORS_WRIST_TEMPERATURE") - .setPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE) - .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_DEPRECATED_2, OPSTR_DEPRECATED_2, "DEPRECATED_2") + .setDefaultMode(AppOpsManager.MODE_IGNORED).build(), new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT, "USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT) - .build() + .build(), + new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED, + "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED, + "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build() }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index be012cf8e202..c0c59a24dd8d 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -473,7 +473,6 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION), new RegularPermission(Manifest.permission.BODY_SENSORS), - new RegularPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE), new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS), }, false), FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */, diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 746b8f70b6af..0b4862176040 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -24,6 +24,7 @@ import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationHistory; import android.app.NotificationManager; +import android.content.AttributionSource; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -225,6 +226,7 @@ interface INotificationManager void setNotificationDelegate(String callingPkg, String delegate); String getNotificationDelegate(String callingPkg); boolean canNotifyAsPackage(String callingPkg, String targetPkg, int userId); + boolean canUseFullScreenIntent(in AttributionSource attributionSource); void setPrivateNotificationsAllowed(boolean allow); boolean getPrivateNotificationsAllowed(); diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index ee242635bfb2..2b1558937d21 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -220,6 +220,20 @@ interface IWallpaperManager { void notifyGoingToSleep(int x, int y, in Bundle extras); /** + * Called when the screen has been fully turned on and is visible. + * + * @hide + */ + void notifyScreenTurnedOn(int displayId); + + /** + * Called when the screen starts turning on. + * + * @hide + */ + void notifyScreenTurningOn(int displayId); + + /** * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. * diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index f80373912dcb..63da0a231286 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -7016,6 +7016,22 @@ public class Notification implements Parcelable } /** + * @return true for custom notifications, including notifications + * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle, + * and other notifications with user-provided custom views. + * + * @hide + */ + public Boolean isCustomNotification() { + if (contentView == null + && bigContentView == null + && headsUpContentView == null) { + return false; + } + return true; + } + + /** * @return true if this notification is showing as a bubble * * @hide diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 80f64e03afe8..785470f2f22e 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -31,7 +31,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.PermissionChecker; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Icon; @@ -877,19 +876,11 @@ public class NotificationManager { * {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}. */ public boolean canUseFullScreenIntent() { - final int result = PermissionChecker.checkPermissionForPreflight(mContext, - android.Manifest.permission.USE_FULL_SCREEN_INTENT, - mContext.getAttributionSource()); - - switch (result) { - case PermissionChecker.PERMISSION_GRANTED: - return true; - case PermissionChecker.PERMISSION_SOFT_DENIED: - case PermissionChecker.PERMISSION_HARD_DENIED: - return false; - default: - if (localLOGV) Log.v(TAG, "Unknown PermissionChecker result: " + result); - return false; + INotificationManager service = getService(); + try { + return service.canUseFullScreenIntent(mContext.getAttributionSource()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 1df860258d82..bc5f7f411af5 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -110,6 +110,9 @@ "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest" } ], "file_patterns": ["(/|^)VoiceInteract[^/]*"] diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java index e933123d08b8..df948f17d6e0 100644 --- a/core/java/android/credentials/CredentialOption.java +++ b/core/java/android/credentials/CredentialOption.java @@ -37,8 +37,7 @@ import java.util.Set; /** * Information about a specific type of credential to be requested during a {@link - * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, - * OutcomeReceiver)} operation. + * CredentialManager#getCredential} operation. */ public final class CredentialOption implements Parcelable { @@ -196,9 +195,8 @@ public final class CredentialOption implements Parcelable { * @throws NullPointerException If {@code credentialRetrievalData}, or * {@code candidateQueryData} is null. * - * @deprecated replaced by Builder + * @hide */ - @Deprecated public CredentialOption( @NonNull String type, @NonNull Bundle credentialRetrievalData, diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java index 852934a808e2..629d578c7358 100644 --- a/core/java/android/credentials/ui/CreateCredentialProviderData.java +++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java @@ -19,6 +19,7 @@ package android.credentials.ui; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -35,7 +36,7 @@ import java.util.List; @TestApi public final class CreateCredentialProviderData extends ProviderData implements Parcelable { @NonNull - private final List<Entry> mSaveEntries; + private final ParceledListSlice<Entry> mSaveEntries; @Nullable private final Entry mRemoteEntry; @@ -43,13 +44,13 @@ public final class CreateCredentialProviderData extends ProviderData implements @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries, @Nullable Entry remoteEntry) { super(providerFlattenedComponentName); - mSaveEntries = saveEntries; + mSaveEntries = new ParceledListSlice<>(saveEntries); mRemoteEntry = remoteEntry; } @NonNull public List<Entry> getSaveEntries() { - return mSaveEntries; + return mSaveEntries.getList(); } @Nullable @@ -60,9 +61,7 @@ public final class CreateCredentialProviderData extends ProviderData implements private CreateCredentialProviderData(@NonNull Parcel in) { super(in); - List<Entry> credentialEntries = new ArrayList<>(); - in.readTypedList(credentialEntries, Entry.CREATOR); - mSaveEntries = credentialEntries; + mSaveEntries = in.readParcelable(null, android.content.pm.ParceledListSlice.class); AnnotationValidations.validate(NonNull.class, null, mSaveEntries); Entry remoteEntry = in.readTypedObject(Entry.CREATOR); @@ -72,7 +71,7 @@ public final class CreateCredentialProviderData extends ProviderData implements @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeTypedList(mSaveEntries); + dest.writeParcelable(mSaveEntries, flags); dest.writeTypedObject(mRemoteEntry, flags); } diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java index e4688a84a3fb..773dee97f7fe 100644 --- a/core/java/android/credentials/ui/GetCredentialProviderData.java +++ b/core/java/android/credentials/ui/GetCredentialProviderData.java @@ -19,6 +19,7 @@ package android.credentials.ui; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -35,11 +36,11 @@ import java.util.List; @TestApi public final class GetCredentialProviderData extends ProviderData implements Parcelable { @NonNull - private final List<Entry> mCredentialEntries; + private final ParceledListSlice<Entry> mCredentialEntries; @NonNull - private final List<Entry> mActionChips; + private final ParceledListSlice<Entry> mActionChips; @NonNull - private final List<AuthenticationEntry> mAuthenticationEntries; + private final ParceledListSlice<AuthenticationEntry> mAuthenticationEntries; @Nullable private final Entry mRemoteEntry; @@ -49,25 +50,25 @@ public final class GetCredentialProviderData extends ProviderData implements Par @NonNull List<AuthenticationEntry> authenticationEntries, @Nullable Entry remoteEntry) { super(providerFlattenedComponentName); - mCredentialEntries = credentialEntries; - mActionChips = actionChips; - mAuthenticationEntries = authenticationEntries; + mCredentialEntries = new ParceledListSlice<>(credentialEntries); + mActionChips = new ParceledListSlice<>(actionChips); + mAuthenticationEntries = new ParceledListSlice<>(authenticationEntries); mRemoteEntry = remoteEntry; } @NonNull public List<Entry> getCredentialEntries() { - return mCredentialEntries; + return mCredentialEntries.getList(); } @NonNull public List<Entry> getActionChips() { - return mActionChips; + return mActionChips.getList(); } @NonNull public List<AuthenticationEntry> getAuthenticationEntries() { - return mAuthenticationEntries; + return mAuthenticationEntries.getList(); } @Nullable @@ -77,20 +78,16 @@ public final class GetCredentialProviderData extends ProviderData implements Par private GetCredentialProviderData(@NonNull Parcel in) { super(in); - - List<Entry> credentialEntries = new ArrayList<>(); - in.readTypedList(credentialEntries, Entry.CREATOR); - mCredentialEntries = credentialEntries; + mCredentialEntries = in.readParcelable(null, + android.content.pm.ParceledListSlice.class); AnnotationValidations.validate(NonNull.class, null, mCredentialEntries); - List<Entry> actionChips = new ArrayList<>(); - in.readTypedList(actionChips, Entry.CREATOR); - mActionChips = actionChips; + mActionChips = in.readParcelable(null, + android.content.pm.ParceledListSlice.class); AnnotationValidations.validate(NonNull.class, null, mActionChips); - List<AuthenticationEntry> authenticationEntries = new ArrayList<>(); - in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR); - mAuthenticationEntries = authenticationEntries; + mAuthenticationEntries = in.readParcelable(null, + android.content.pm.ParceledListSlice.class); AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries); Entry remoteEntry = in.readTypedObject(Entry.CREATOR); @@ -100,9 +97,9 @@ public final class GetCredentialProviderData extends ProviderData implements Par @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeTypedList(mCredentialEntries); - dest.writeTypedList(mActionChips); - dest.writeTypedList(mAuthenticationEntries); + dest.writeParcelable(mCredentialEntries, flags); + dest.writeParcelable(mActionChips, flags); + dest.writeParcelable(mAuthenticationEntries, flags); dest.writeTypedObject(mRemoteEntry, flags); } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index fa678fc5ee1a..2e40f6096ccb 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -142,6 +142,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan private PromptInfo mPromptInfo; private ButtonInfo mNegativeButtonInfo; private Context mContext; + private IAuthService mService; /** * Creates a builder for a {@link BiometricPrompt} dialog. @@ -212,6 +213,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * @param service + * @return This builder. + * @hide + */ + @RequiresPermission(TEST_BIOMETRIC) + @NonNull + public Builder setService(@NonNull IAuthService service) { + mService = service; + return this; + } + + /** * Sets an optional title, subtitle, and/or description that will override other text when * the user is authenticating with PIN/pattern/password. Currently for internal use only. * @return This builder. @@ -472,7 +485,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan throw new IllegalArgumentException("Can't have both negative button behavior" + " and device credential enabled"); } - return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo); + mService = (mService == null) ? IAuthService.Stub.asInterface( + ServiceManager.getService(Context.AUTH_SERVICE)) : mService; + return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService); } } @@ -521,7 +536,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public void onAuthenticationFailed() { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationFailed(); - mIsPromptShowing = false; }); } @@ -604,12 +618,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan private boolean mIsPromptShowing; - private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) { + private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo, + IAuthService service) { mContext = context; mPromptInfo = promptInfo; mNegativeButtonInfo = negativeButtonInfo; - mService = IAuthService.Stub.asInterface( - ServiceManager.getService(Context.AUTH_SERVICE)); + mService = service; mIsPromptShowing = false; } diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 5feda785ece3..ad68866571e3 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -1310,6 +1310,10 @@ public abstract class CameraDevice implements AutoCloseable { * {@link Surface}, submitting a reprocess {@link CaptureRequest} with multiple * output targets will result in a {@link CaptureFailure}. * + * From Android 14 onward, {@link CaptureRequest#CONTROL_CAPTURE_INTENT} will be set to + * {@link CameraMetadata#CONTROL_CAPTURE_INTENT_STILL_CAPTURE} by default. Prior to Android 14, + * apps will need to explicitly set this key themselves. + * * @param inputResult The capture result of the output image or one of the output images used * to generate the reprocess input image for this capture request. * diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index cb1efe8c2b55..f2d8caaab0e7 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -26,6 +26,7 @@ import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraExtensionCharacteristics; +import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraOfflineSession; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; @@ -861,8 +862,13 @@ public class CameraDeviceImpl extends CameraDevice CameraMetadataNative resultMetadata = new CameraMetadataNative(inputResult.getNativeCopy()); - return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true, - inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null); + CaptureRequest.Builder builder = new CaptureRequest.Builder(resultMetadata, + /*reprocess*/true, inputResult.getSessionId(), getId(), + /*physicalCameraIdSet*/ null); + builder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, + CameraMetadata.CONTROL_CAPTURE_INTENT_STILL_CAPTURE); + + return builder; } } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 244632a87593..9f9c2222f9d9 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1222,7 +1222,7 @@ public class Build { /** * Upside Down Cake. */ - public static final int UPSIDE_DOWN_CAKE = CUR_DEVELOPMENT; + public static final int UPSIDE_DOWN_CAKE = 34; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index d34b45bf1ff1..4603e43fd164 100644 --- a/core/java/android/permission/OWNERS +++ b/core/java/android/permission/OWNERS @@ -1,18 +1,19 @@ # Bug component: 137825 -evanseverson@google.com -evanxinchen@google.com ashfall@google.com -guojing@google.com +augale@google.com +evanseverson@google.com +fayey@google.com jaysullivan@google.com +joecastro@google.com kvakil@google.com mrulhania@google.com narayan@google.com ntmyren@google.com olekarg@google.com pyuli@google.com -raphk@google.com rmacgregor@google.com sergeynv@google.com theianchen@google.com +yutingfang@google.com zhanghai@google.com diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index d2f9ff01ca98..59b945c9c9a4 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -2051,6 +2051,14 @@ public final class Telephony { * <P>Type: TEXT</P> */ public static final String ADDRESS = "address"; + + /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; } /** @@ -2119,6 +2127,14 @@ public final class Telephony { * <P>Type: INTEGER (boolean)</P> */ public static final String ARCHIVED = "archived"; + + /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; } /** @@ -2477,6 +2493,14 @@ public final class Telephony { public static final String CHARSET = "charset"; /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; + + /** * Generates a Addr {@link Uri} for message, used to perform Addr table operation * for mms. * @@ -2597,6 +2621,14 @@ public final class Telephony { public static final String TEXT = "text"; /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; + + /** * Generates a Part {@link Uri} for message, used to perform Part table operation * for mms. * @@ -2635,6 +2667,14 @@ public final class Telephony { * <P>Type: INTEGER (long)</P> */ public static final String SENT_TIME = "sent_time"; + + /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; } /** @@ -2868,6 +2908,14 @@ public final class Telephony { * <P>Type: TEXT</P> */ public static final String INDEXED_TEXT = "index_text"; + + /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; } } diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java index cd53cb6afc71..df934335e49d 100644 --- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java +++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -33,7 +34,7 @@ import java.util.Objects; * Response to a {@link BeginCreateCredentialRequest}. */ public final class BeginCreateCredentialResponse implements Parcelable { - private final @NonNull List<CreateEntry> mCreateEntries; + private final @NonNull ParceledListSlice<CreateEntry> mCreateEntries; private final @Nullable RemoteEntry mRemoteCreateEntry; /** @@ -41,19 +42,19 @@ public final class BeginCreateCredentialResponse implements Parcelable { * to return. */ public BeginCreateCredentialResponse() { - this(/*createEntries=*/new ArrayList<>(), /*remoteCreateEntry=*/null); + this(/*createEntries=*/new ParceledListSlice<>(new ArrayList<>()), + /*remoteCreateEntry=*/null); } private BeginCreateCredentialResponse(@NonNull Parcel in) { - List<CreateEntry> createEntries = new ArrayList<>(); - in.readTypedList(createEntries, CreateEntry.CREATOR); - mCreateEntries = createEntries; + mCreateEntries = in.readParcelable( + null, android.content.pm.ParceledListSlice.class); mRemoteCreateEntry = in.readTypedObject(RemoteEntry.CREATOR); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeTypedList(mCreateEntries); + dest.writeParcelable(mCreateEntries, flags); dest.writeTypedObject(mRemoteCreateEntry, flags); } @@ -76,7 +77,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { }; /* package-private */ BeginCreateCredentialResponse( - @NonNull List<CreateEntry> createEntries, + @NonNull ParceledListSlice<CreateEntry> createEntries, @Nullable RemoteEntry remoteCreateEntry) { this.mCreateEntries = createEntries; com.android.internal.util.AnnotationValidations.validate( @@ -86,7 +87,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { /** Returns the list of create entries to be displayed on the UI. */ public @NonNull List<CreateEntry> getCreateEntries() { - return mCreateEntries; + return mCreateEntries.getList(); } /** Returns the remote create entry to be displayed on the UI. */ @@ -159,7 +160,9 @@ public final class BeginCreateCredentialResponse implements Parcelable { * Builds a new instance of {@link BeginCreateCredentialResponse}. */ public @NonNull BeginCreateCredentialResponse build() { - return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry); + return new BeginCreateCredentialResponse( + new ParceledListSlice<>(mCreateEntries), + mRemoteCreateEntry); } } } diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java index e25b6869605d..5ed06ac1ade7 100644 --- a/core/java/android/service/credentials/BeginGetCredentialResponse.java +++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -35,13 +36,13 @@ import java.util.Objects; */ public final class BeginGetCredentialResponse implements Parcelable { /** List of credential entries to be displayed on the UI. */ - private final @NonNull List<CredentialEntry> mCredentialEntries; + private final @NonNull ParceledListSlice<CredentialEntry> mCredentialEntries; /** List of authentication entries to be displayed on the UI. */ - private final @NonNull List<Action> mAuthenticationEntries; + private final @NonNull ParceledListSlice<Action> mAuthenticationEntries; /** List of provider actions to be displayed on the UI. */ - private final @NonNull List<Action> mActions; + private final @NonNull ParceledListSlice<Action> mActions; /** Remote credential entry to get the response from a different device. */ private final @Nullable RemoteEntry mRemoteCredentialEntry; @@ -51,31 +52,30 @@ public final class BeginGetCredentialResponse implements Parcelable { * or {@link Action} to return. */ public BeginGetCredentialResponse() { - this(/*credentialEntries=*/new ArrayList<>(), - /*authenticationActions=*/new ArrayList<>(), - /*actions=*/new ArrayList<>(), + this(/*credentialEntries=*/new ParceledListSlice<>(new ArrayList<>()), + /*authenticationEntries=*/new ParceledListSlice<>(new ArrayList<>()), + /*actions=*/new ParceledListSlice<>(new ArrayList<>()), /*remoteCredentialEntry=*/null); } - private BeginGetCredentialResponse(@NonNull List<CredentialEntry> credentialEntries, - @NonNull List<Action> authenticationEntries, @NonNull List<Action> actions, + private BeginGetCredentialResponse( + @NonNull ParceledListSlice<CredentialEntry> credentialEntries, + @NonNull ParceledListSlice<Action> authenticationEntries, + @NonNull ParceledListSlice<Action> actions, @Nullable RemoteEntry remoteCredentialEntry) { - mCredentialEntries = new ArrayList<>(credentialEntries); - mAuthenticationEntries = new ArrayList<>(authenticationEntries); - mActions = new ArrayList<>(actions); + mCredentialEntries = credentialEntries; + mAuthenticationEntries = authenticationEntries; + mActions = actions; mRemoteCredentialEntry = remoteCredentialEntry; } private BeginGetCredentialResponse(@NonNull Parcel in) { - List<CredentialEntry> credentialEntries = new ArrayList<>(); - in.readTypedList(credentialEntries, CredentialEntry.CREATOR); - mCredentialEntries = credentialEntries; - List<Action> authenticationEntries = new ArrayList<>(); - in.readTypedList(authenticationEntries, Action.CREATOR); - mAuthenticationEntries = authenticationEntries; - List<Action> actions = new ArrayList<>(); - in.readTypedList(actions, Action.CREATOR); - mActions = actions; + mCredentialEntries = in.readParcelable( + null, android.content.pm.ParceledListSlice.class); + mAuthenticationEntries = in.readParcelable( + null, android.content.pm.ParceledListSlice.class); + mActions = in.readParcelable( + null, android.content.pm.ParceledListSlice.class); mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR); } @@ -99,9 +99,9 @@ public final class BeginGetCredentialResponse implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeTypedList(mCredentialEntries, flags); - dest.writeTypedList(mAuthenticationEntries, flags); - dest.writeTypedList(mActions, flags); + dest.writeParcelable(mCredentialEntries, flags); + dest.writeParcelable(mAuthenticationEntries, flags); + dest.writeParcelable(mActions, flags); dest.writeTypedObject(mRemoteCredentialEntry, flags); } @@ -109,21 +109,22 @@ public final class BeginGetCredentialResponse implements Parcelable { * Returns the list of credential entries to be displayed on the UI. */ public @NonNull List<CredentialEntry> getCredentialEntries() { - return mCredentialEntries; + return mCredentialEntries.getList(); } /** * Returns the list of authentication entries to be displayed on the UI. */ public @NonNull List<Action> getAuthenticationActions() { - return mAuthenticationEntries; + return mAuthenticationEntries.getList(); } /** * Returns the list of actions to be displayed on the UI. */ public @NonNull List<Action> getActions() { - return mActions; + + return mActions.getList(); } /** @@ -268,8 +269,11 @@ public final class BeginGetCredentialResponse implements Parcelable { * Builds a {@link BeginGetCredentialResponse} instance. */ public @NonNull BeginGetCredentialResponse build() { - return new BeginGetCredentialResponse(mCredentialEntries, mAuthenticationEntries, - mActions, mRemoteCredentialEntry); + return new BeginGetCredentialResponse( + new ParceledListSlice<>(mCredentialEntries), + new ParceledListSlice<>(mAuthenticationEntries), + new ParceledListSlice<>(mActions), + mRemoteCredentialEntry); } } } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 402da28b3c5c..828c062d955d 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -45,7 +45,6 @@ import android.provider.Settings.Global; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.PluralsMessageFormatter; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -59,7 +58,6 @@ 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.Calendar; import java.util.Date; @@ -310,86 +308,6 @@ public class ZenModeConfig implements Parcelable { return buffer.toString(); } - public Diff diff(ZenModeConfig to) { - final Diff d = new Diff(); - if (to == null) { - return d.addLine("config", "delete"); - } - if (user != to.user) { - d.addLine("user", user, to.user); - } - if (allowAlarms != to.allowAlarms) { - d.addLine("allowAlarms", allowAlarms, to.allowAlarms); - } - if (allowMedia != to.allowMedia) { - d.addLine("allowMedia", allowMedia, to.allowMedia); - } - if (allowSystem != to.allowSystem) { - d.addLine("allowSystem", allowSystem, to.allowSystem); - } - if (allowCalls != to.allowCalls) { - d.addLine("allowCalls", allowCalls, to.allowCalls); - } - if (allowReminders != to.allowReminders) { - d.addLine("allowReminders", allowReminders, to.allowReminders); - } - if (allowEvents != to.allowEvents) { - d.addLine("allowEvents", allowEvents, to.allowEvents); - } - if (allowRepeatCallers != to.allowRepeatCallers) { - d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers); - } - if (allowMessages != to.allowMessages) { - d.addLine("allowMessages", allowMessages, to.allowMessages); - } - if (allowCallsFrom != to.allowCallsFrom) { - d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom); - } - if (allowMessagesFrom != to.allowMessagesFrom) { - d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom); - } - if (suppressedVisualEffects != to.suppressedVisualEffects) { - d.addLine("suppressedVisualEffects", suppressedVisualEffects, - to.suppressedVisualEffects); - } - final ArraySet<String> allRules = new ArraySet<>(); - addKeys(allRules, automaticRules); - addKeys(allRules, to.automaticRules); - final int N = allRules.size(); - for (int i = 0; i < N; i++) { - final String rule = allRules.valueAt(i); - final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null; - final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null; - ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule); - } - ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule); - - if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) { - d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd, - to.areChannelsBypassingDnd); - } - return d; - } - - public static Diff diff(ZenModeConfig from, ZenModeConfig to) { - if (from == null) { - final Diff d = new Diff(); - if (to != null) { - d.addLine("config", "insert"); - } - return d; - } - return from.diff(to); - } - - private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) { - if (map != null) { - for (int i = 0; i < map.size(); i++) { - set.add(map.keyAt(i)); - } - } - } - public boolean isValid() { if (!isValidManualRule(manualRule)) return false; final int N = automaticRules.size(); @@ -1922,66 +1840,6 @@ public class ZenModeConfig implements Parcelable { proto.end(token); } - private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) { - if (d == null) return; - if (from == null) { - if (to != null) { - d.addLine(item, "insert"); - } - return; - } - from.appendDiff(d, item, to); - } - - private void appendDiff(Diff d, String item, ZenRule to) { - if (to == null) { - d.addLine(item, "delete"); - return; - } - if (enabled != to.enabled) { - d.addLine(item, "enabled", enabled, to.enabled); - } - if (snoozing != to.snoozing) { - d.addLine(item, "snoozing", snoozing, to.snoozing); - } - if (!Objects.equals(name, to.name)) { - d.addLine(item, "name", name, to.name); - } - if (zenMode != to.zenMode) { - d.addLine(item, "zenMode", zenMode, to.zenMode); - } - if (!Objects.equals(conditionId, to.conditionId)) { - d.addLine(item, "conditionId", conditionId, to.conditionId); - } - if (!Objects.equals(condition, to.condition)) { - d.addLine(item, "condition", condition, to.condition); - } - if (!Objects.equals(component, to.component)) { - d.addLine(item, "component", component, to.component); - } - if (!Objects.equals(configurationActivity, to.configurationActivity)) { - d.addLine(item, "configActivity", configurationActivity, to.configurationActivity); - } - if (!Objects.equals(id, to.id)) { - d.addLine(item, "id", id, to.id); - } - if (creationTime != to.creationTime) { - d.addLine(item, "creationTime", creationTime, to.creationTime); - } - if (!Objects.equals(enabler, to.enabler)) { - d.addLine(item, "enabler", enabler, to.enabler); - } - if (!Objects.equals(zenPolicy, to.zenPolicy)) { - d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy); - } - if (modified != to.modified) { - d.addLine(item, "modified", modified, to.modified); - } - if (!Objects.equals(pkg, to.pkg)) { - d.addLine(item, "pkg", pkg, to.pkg); - } - } - @Override public boolean equals(@Nullable Object o) { if (!(o instanceof ZenRule)) return false; @@ -2040,40 +1898,6 @@ public class ZenModeConfig implements Parcelable { }; } - public static class Diff { - private final ArrayList<String> lines = new ArrayList<>(); - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("Diff["); - final int N = lines.size(); - for (int i = 0; i < N; i++) { - if (i > 0) { - sb.append(",\n"); - } - sb.append(lines.get(i)); - } - return sb.append(']').toString(); - } - - private Diff addLine(String item, String action) { - lines.add(item + ":" + action); - return this; - } - - public Diff addLine(String item, String subitem, Object from, Object to) { - return addLine(item + "." + subitem, from, to); - } - - public Diff addLine(String item, Object from, Object to) { - return addLine(item, from + "->" + to); - } - - public boolean isEmpty() { - return lines.isEmpty(); - } - } - /** * Determines whether dnd behavior should mute all ringer-controlled sounds * This includes notification, ringer and system sounds diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java new file mode 100644 index 000000000000..c7b89eb284b6 --- /dev/null +++ b/core/java/android/service/notification/ZenModeDiff.java @@ -0,0 +1,542 @@ +/* + * 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.service.notification; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.util.ArrayMap; +import android.util.ArraySet; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.Set; + +/** + * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their + * subcomponents (automatic and manual ZenRules). + * @hide + */ +public class ZenModeDiff { + /** + * Enum representing whether the existence of a config or rule has changed (added or removed, + * or "none" meaning there is no change, which may either mean both null, or there exists a + * diff in fields rather than add/remove). + */ + @IntDef(value = { + NONE, + ADDED, + REMOVED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ExistenceChange{} + + public static final int NONE = 0; + public static final int ADDED = 1; + public static final int REMOVED = 2; + + /** + * Diff class representing an individual field diff. + * @param <T> The type of the field. + */ + public static class FieldDiff<T> { + private final T mFrom; + private final T mTo; + + /** + * Constructor to create a FieldDiff object with the given values. + * @param from from (old) value + * @param to to (new) value + */ + public FieldDiff(@Nullable T from, @Nullable T to) { + mFrom = from; + mTo = to; + } + + /** + * Get the "from" value + */ + public T from() { + return mFrom; + } + + /** + * Get the "to" value + */ + public T to() { + return mTo; + } + + /** + * Get the string representation of this field diff, in the form of "from->to". + */ + @Override + public String toString() { + return mFrom + "->" + mTo; + } + + /** + * Returns whether this represents an actual diff. + */ + public boolean hasDiff() { + // note that Objects.equals handles null values gracefully. + return !Objects.equals(mFrom, mTo); + } + } + + /** + * Base diff class that contains info about whether something was added, and a set of named + * fields that changed. + * Extend for diffs of specific types of objects. + */ + private abstract static class BaseDiff { + // Whether the diff was added or removed + @ExistenceChange private int mExists = NONE; + + // Map from field name to diffs for any standalone fields in the object. + private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>(); + + // Functions for actually diffing objects and string representations have to be implemented + // by subclasses. + + /** + * Return whether this diff represents any changes. + */ + public abstract boolean hasDiff(); + + /** + * Return a string representation of the diff. + */ + public abstract String toString(); + + /** + * Constructor that takes the two objects meant to be compared. This constructor sets + * whether there is an existence change (added or removed). + * @param from previous Object + * @param to new Object + */ + BaseDiff(Object from, Object to) { + if (from == null) { + if (to != null) { + mExists = ADDED; + } + // If both are null, there isn't an existence change; callers/inheritors must handle + // the both null case. + } else if (to == null) { + // in this case, we know that from != null + mExists = REMOVED; + } + + // Subclasses should implement the actual diffing functionality in their own + // constructors. + } + + /** + * Add a diff for a specific field to the map. + * @param name field name + * @param diff FieldDiff object representing the diff + */ + final void addField(String name, FieldDiff diff) { + mFields.put(name, diff); + } + + /** + * Returns whether this diff represents a config being newly added. + */ + public final boolean wasAdded() { + return mExists == ADDED; + } + + /** + * Returns whether this diff represents a config being removed. + */ + public final boolean wasRemoved() { + return mExists == REMOVED; + } + + /** + * Returns whether this diff represents an object being either added or removed. + */ + public final boolean hasExistenceChange() { + return mExists != NONE; + } + + /** + * Returns whether there are any individual field diffs. + */ + public final boolean hasFieldDiffs() { + return mFields.size() > 0; + } + + /** + * Returns the diff for the specific named field if it exists + */ + public final FieldDiff getDiffForField(String name) { + return mFields.getOrDefault(name, null); + } + + /** + * Get the set of all field names with some diff. + */ + public final Set<String> fieldNamesWithDiff() { + return mFields.keySet(); + } + } + + /** + * Diff class representing a diff between two ZenModeConfigs. + */ + public static class ConfigDiff extends BaseDiff { + // Rules. Automatic rule map is keyed by the rule name. + private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>(); + private RuleDiff mManualRuleDiff; + + // Helpers for string generation + private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom"; + private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom"; + private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom"; + private static final Set<String> PEOPLE_TYPE_FIELDS = + Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD); + + /** + * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs. + * + * @param from previous ZenModeConfig + * @param to new ZenModeConfig + */ + public ConfigDiff(ZenModeConfig from, ZenModeConfig to) { + super(from, to); + // If both are null skip + if (from == null && to == null) { + return; + } + if (hasExistenceChange()) { + // either added or removed; return here. otherwise (they're not both null) there's + // field diffs. + return; + } + + // Now we compare all the fields, knowing there's a diff and that neither is null + if (from.user != to.user) { + addField("user", new FieldDiff<>(from.user, to.user)); + } + if (from.allowAlarms != to.allowAlarms) { + addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms)); + } + if (from.allowMedia != to.allowMedia) { + addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia)); + } + if (from.allowSystem != to.allowSystem) { + addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem)); + } + if (from.allowCalls != to.allowCalls) { + addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls)); + } + if (from.allowReminders != to.allowReminders) { + addField("allowReminders", + new FieldDiff<>(from.allowReminders, to.allowReminders)); + } + if (from.allowEvents != to.allowEvents) { + addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents)); + } + if (from.allowRepeatCallers != to.allowRepeatCallers) { + addField("allowRepeatCallers", + new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers)); + } + if (from.allowMessages != to.allowMessages) { + addField("allowMessages", + new FieldDiff<>(from.allowMessages, to.allowMessages)); + } + if (from.allowConversations != to.allowConversations) { + addField("allowConversations", + new FieldDiff<>(from.allowConversations, to.allowConversations)); + } + if (from.allowCallsFrom != to.allowCallsFrom) { + addField("allowCallsFrom", + new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom)); + } + if (from.allowMessagesFrom != to.allowMessagesFrom) { + addField("allowMessagesFrom", + new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom)); + } + if (from.allowConversationsFrom != to.allowConversationsFrom) { + addField("allowConversationsFrom", + new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom)); + } + if (from.suppressedVisualEffects != to.suppressedVisualEffects) { + addField("suppressedVisualEffects", + new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects)); + } + if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) { + addField("areChannelsBypassingDnd", + new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd)); + } + + // Compare automatic and manual rules + final ArraySet<String> allRules = new ArraySet<>(); + addKeys(allRules, from.automaticRules); + addKeys(allRules, to.automaticRules); + final int num = allRules.size(); + for (int i = 0; i < num; i++) { + final String rule = allRules.valueAt(i); + final ZenModeConfig.ZenRule + fromRule = from.automaticRules != null ? from.automaticRules.get(rule) + : null; + final ZenModeConfig.ZenRule + toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null; + RuleDiff ruleDiff = new RuleDiff(fromRule, toRule); + if (ruleDiff.hasDiff()) { + mAutomaticRulesDiff.put(rule, ruleDiff); + } + } + // If there's no diff this may turn out to be null, but that's also fine + RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule); + if (manualRuleDiff.hasDiff()) { + mManualRuleDiff = manualRuleDiff; + } + } + + private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) { + if (map != null) { + for (int i = 0; i < map.size(); i++) { + set.add(map.keyAt(i)); + } + } + } + + /** + * Returns whether this diff object contains any diffs in any field. + */ + @Override + public boolean hasDiff() { + return hasExistenceChange() + || hasFieldDiffs() + || mManualRuleDiff != null + || mAutomaticRulesDiff.size() > 0; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Diff["); + if (!hasDiff()) { + sb.append("no changes"); + } + + // If added or deleted, then that's just the end of it + if (hasExistenceChange()) { + if (wasAdded()) { + sb.append("added"); + } else if (wasRemoved()) { + sb.append("removed"); + } + } + + // Handle top-level field change + boolean first = true; + for (String key : fieldNamesWithDiff()) { + FieldDiff diff = getDiffForField(key); + if (diff == null) { + // this shouldn't happen, but + continue; + } + if (first) { + first = false; + } else { + sb.append(",\n"); + } + + // Some special handling for people- and conversation-type fields for readability + if (PEOPLE_TYPE_FIELDS.contains(key)) { + sb.append(key); + sb.append(":"); + sb.append(ZenModeConfig.sourceToString((int) diff.from())); + sb.append("->"); + sb.append(ZenModeConfig.sourceToString((int) diff.to())); + } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) { + sb.append(key); + sb.append(":"); + sb.append(ZenPolicy.conversationTypeToString((int) diff.from())); + sb.append("->"); + sb.append(ZenPolicy.conversationTypeToString((int) diff.to())); + } else { + sb.append(key); + sb.append(":"); + sb.append(diff); + } + } + + // manual rule + if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) { + if (first) { + first = false; + } else { + sb.append(",\n"); + } + sb.append("manualRule:"); + sb.append(mManualRuleDiff); + } + + // automatic rules + for (String rule : mAutomaticRulesDiff.keySet()) { + RuleDiff diff = mAutomaticRulesDiff.get(rule); + if (diff != null && diff.hasDiff()) { + if (first) { + first = false; + } else { + sb.append(",\n"); + } + sb.append("automaticRule["); + sb.append(rule); + sb.append("]:"); + sb.append(diff); + } + } + + return sb.append(']').toString(); + } + + /** + * Get the diff in manual rule, if it exists. + */ + public RuleDiff getManualRuleDiff() { + return mManualRuleDiff; + } + + /** + * Get the full map of automatic rule diffs, or null if there are no diffs. + */ + public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() { + return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null; + } + } + + /** + * Diff class representing a change between two ZenRules. + */ + public static class RuleDiff extends BaseDiff { + /** + * Create a RuleDiff representing the difference between two ZenRule objects. + * @param from previous ZenRule + * @param to new ZenRule + * @return The diff between the two given ZenRules + */ + public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) { + super(from, to); + // Short-circuit the both-null case + if (from == null && to == null) { + return; + } + // Return if the diff was added or removed + if (hasExistenceChange()) { + return; + } + + if (from.enabled != to.enabled) { + addField("enabled", new FieldDiff<>(from.enabled, to.enabled)); + } + if (from.snoozing != to.snoozing) { + addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing)); + } + if (!Objects.equals(from.name, to.name)) { + addField("name", new FieldDiff<>(from.name, to.name)); + } + if (from.zenMode != to.zenMode) { + addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode)); + } + if (!Objects.equals(from.conditionId, to.conditionId)) { + addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId)); + } + if (!Objects.equals(from.condition, to.condition)) { + addField("condition", new FieldDiff<>(from.condition, to.condition)); + } + if (!Objects.equals(from.component, to.component)) { + addField("component", new FieldDiff<>(from.component, to.component)); + } + if (!Objects.equals(from.configurationActivity, to.configurationActivity)) { + addField("configurationActivity", new FieldDiff<>( + from.configurationActivity, to.configurationActivity)); + } + if (!Objects.equals(from.id, to.id)) { + addField("id", new FieldDiff<>(from.id, to.id)); + } + if (from.creationTime != to.creationTime) { + addField("creationTime", + new FieldDiff<>(from.creationTime, to.creationTime)); + } + if (!Objects.equals(from.enabler, to.enabler)) { + addField("enabler", new FieldDiff<>(from.enabler, to.enabler)); + } + if (!Objects.equals(from.zenPolicy, to.zenPolicy)) { + addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy)); + } + if (from.modified != to.modified) { + addField("modified", new FieldDiff<>(from.modified, to.modified)); + } + if (!Objects.equals(from.pkg, to.pkg)) { + addField("pkg", new FieldDiff<>(from.pkg, to.pkg)); + } + } + + /** + * Returns whether this object represents an actual diff. + */ + @Override + public boolean hasDiff() { + return hasExistenceChange() || hasFieldDiffs(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ZenRuleDiff{"); + // If there's no diff, probably we haven't actually let this object continue existing + // but might as well handle this case. + if (!hasDiff()) { + sb.append("no changes"); + } + + // If added or deleted, then that's just the end of it + if (hasExistenceChange()) { + if (wasAdded()) { + sb.append("added"); + } else if (wasRemoved()) { + sb.append("removed"); + } + } + + // Go through all of the individual fields + boolean first = true; + for (String key : fieldNamesWithDiff()) { + FieldDiff diff = getDiffForField(key); + if (diff == null) { + // this shouldn't happen, but + continue; + } + if (first) { + first = false; + } else { + sb.append(", "); + } + + sb.append(key); + sb.append(":"); + sb.append(diff); + } + + return sb.append("}").toString(); + } + } +} diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index 4c51be0ab8d9..f1ae22eca873 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -32,6 +32,8 @@ interface IWallpaperEngine { oneway void setDisplayPadding(in Rect padding); @UnsupportedAppUsage oneway void setVisibility(boolean visible); + oneway void onScreenTurningOn(); + oneway void onScreenTurnedOn(); oneway void setInAmbientMode(boolean inAmbientDisplay, long animationDuration); @UnsupportedAppUsage oneway void dispatchPointer(in MotionEvent event); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 0b947fc18237..77bbeb59927a 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -168,6 +168,7 @@ public abstract class WallpaperService extends Service { private static final int MSG_ZOOM = 10100; private static final int MSG_RESIZE_PREVIEW = 10110; private static final int MSG_REPORT_SHOWN = 10150; + private static final int MSG_UPDATE_SCREEN_TURNING_ON = 10170; private static final int MSG_UPDATE_DIMMING = 10200; private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210; @@ -213,6 +214,16 @@ public abstract class WallpaperService extends Service { boolean mInitializing = true; boolean mVisible; + /** + * Whether the screen is turning on. + * After the display is powered on, brightness is initially off. It is turned on only after + * all windows have been drawn, and sysui notifies that it's ready (See + * {@link com.android.internal.policy.IKeyguardDrawnCallback}). + * As some wallpapers use visibility as a signal to start animations, this makes sure + * {@link Engine#onVisibilityChanged} is invoked only when the display is both on and + * visible (with brightness on). + */ + private boolean mIsScreenTurningOn; boolean mReportedVisible; boolean mDestroyed; // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false @@ -1018,6 +1029,7 @@ public abstract class WallpaperService extends Service { out.print(" mDestroyed="); out.println(mDestroyed); out.print(prefix); out.print("mVisible="); out.print(mVisible); out.print(" mReportedVisible="); out.println(mReportedVisible); + out.print(" mIsScreenTurningOn="); out.println(mIsScreenTurningOn); out.print(prefix); out.print("mDisplay="); out.println(mDisplay); out.print(prefix); out.print("mCreated="); out.print(mCreated); out.print(" mSurfaceCreated="); out.print(mSurfaceCreated); @@ -1549,6 +1561,13 @@ public abstract class WallpaperService extends Service { } } + void onScreenTurningOnChanged(boolean isScreenTurningOn) { + if (!mDestroyed) { + mIsScreenTurningOn = isScreenTurningOn; + reportVisibility(false); + } + } + void doVisibilityChanged(boolean visible) { if (!mDestroyed) { mVisible = visible; @@ -1565,9 +1584,10 @@ public abstract class WallpaperService extends Service { return; } if (!mDestroyed) { - mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : - mDisplay.getCommittedState(); - boolean visible = mVisible && mDisplayState != Display.STATE_OFF; + mDisplayState = + mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState(); + boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn; + boolean visible = mVisible && displayVisible; if (DEBUG) { Log.v( TAG, @@ -2486,6 +2506,20 @@ public abstract class WallpaperService extends Service { } } + public void updateScreenTurningOn(boolean isScreenTurningOn) { + Message msg = mCaller.obtainMessageBO(MSG_UPDATE_SCREEN_TURNING_ON, isScreenTurningOn, + null); + mCaller.sendMessage(msg); + } + + public void onScreenTurningOn() throws RemoteException { + updateScreenTurningOn(true); + } + + public void onScreenTurnedOn() throws RemoteException { + updateScreenTurningOn(false); + } + @Override public void executeMessage(Message message) { switch (message.what) { @@ -2530,6 +2564,13 @@ public abstract class WallpaperService extends Service { + ": " + message.arg1); mEngine.doVisibilityChanged(message.arg1 != 0); break; + case MSG_UPDATE_SCREEN_TURNING_ON: + if (DEBUG) { + Log.v(TAG, + message.arg1 != 0 ? "Screen turning on" : "Screen turned on"); + } + mEngine.onScreenTurningOnChanged(/* isScreenTurningOn= */ message.arg1 != 0); + break; case MSG_WALLPAPER_OFFSETS: { mEngine.doOffsetsChanged(true); } break; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index c3295088ef11..d87198a0dc85 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -128,6 +128,12 @@ public class FeatureFlagUtils { */ public static final String SETTINGS_ENABLE_SPA_PHASE2 = "settings_enable_spa_phase2"; + /** + * Enable the SPA metrics writing. + * @hide + */ + public static final String SETTINGS_ENABLE_SPA_METRICS = "settings_enable_spa_metrics"; + /** Flag to enable/disable adb log metrics * @hide */ @@ -226,6 +232,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false"); + DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "false"); DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index c92b1b8c120d..5dd2d82200bc 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -195,7 +195,7 @@ public final class Choreographer { private boolean mDebugPrintNextFrameTimeDelta; private int mFPSDivisor = 1; - private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData = + private DisplayEventReceiver.VsyncEventData mLastVsyncEventData = new DisplayEventReceiver.VsyncEventData(); private final FrameData mFrameData = new FrameData(); @@ -785,12 +785,13 @@ public final class Choreographer { DisplayEventReceiver.VsyncEventData vsyncEventData) { final long startNanos; final long frameIntervalNanos = vsyncEventData.frameInterval; + boolean resynced = false; try { + FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId); + Trace.traceBegin( + Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId); } - mFrameData.update(frameTimeNanos, vsyncEventData); synchronized (mLock) { if (!mFrameScheduled) { traceMessage("Frame not scheduled"); @@ -828,7 +829,9 @@ public final class Choreographer { + " ms in the past."); } } - mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos); + timeline = mFrameData.update( + frameTimeNanos, mDisplayEventReceiver, jitterNanos); + resynced = true; } if (frameTimeNanos < mLastFrameTimeNanos) { @@ -857,7 +860,13 @@ public final class Choreographer { mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos; - mLastVsyncEventData.copyFrom(vsyncEventData); + mLastVsyncEventData = vsyncEventData; + } + + if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + String message = String.format("Choreographer#doFrame - resynced to %d in %.1fms", + timeline.mVsyncId, (timeline.mDeadlineNanos - startNanos) * 0.000001f); + Trace.traceBegin(Trace.TRACE_TAG_VIEW, message); } AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); @@ -875,6 +884,9 @@ public final class Choreographer { doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos); } finally { AnimationUtils.unlockAnimationClock(); + if (resynced) { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } @@ -1149,7 +1161,8 @@ public final class Choreographer { * Update the frame data with a {@code DisplayEventReceiver.VsyncEventData} received from * native. */ - void update(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { + FrameTimeline update( + long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { if (vsyncEventData.frameTimelines.length != mFrameTimelines.length) { throw new IllegalStateException( "Length of native frame timelines received does not match Java. Did " @@ -1164,6 +1177,7 @@ public final class Choreographer { mFrameTimelines[i].update(frameTimeline.vsyncId, frameTimeline.expectedPresentationTime, frameTimeline.deadline); } + return mFrameTimelines[mPreferredFrameTimelineIndex]; } /** @@ -1171,7 +1185,7 @@ public final class Choreographer { * * @param jitterNanos currentTime - frameTime */ - void update( + FrameTimeline update( long frameTimeNanos, DisplayEventReceiver displayEventReceiver, long jitterNanos) { int newPreferredIndex = 0; final long minimumDeadline = @@ -1192,6 +1206,7 @@ public final class Choreographer { } else { update(frameTimeNanos, newPreferredIndex); } + return mFrameTimelines[mPreferredFrameTimelineIndex]; } void update(long frameTimeNanos, int newPreferredFrameTimelineIndex) { @@ -1247,7 +1262,7 @@ public final class Choreographer { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; - private final VsyncEventData mLastVsyncEventData = new VsyncEventData(); + private VsyncEventData mLastVsyncEventData = new VsyncEventData(); FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) { super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle); @@ -1287,7 +1302,7 @@ public final class Choreographer { mTimestampNanos = timestampNanos; mFrame = frame; - mLastVsyncEventData.copyFrom(vsyncEventData); + mLastVsyncEventData = vsyncEventData; Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 54db34e788e9..03074894b2ff 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -81,10 +81,7 @@ public abstract class DisplayEventReceiver { // GC'd while the native peer of the receiver is using them. private MessageQueue mMessageQueue; - private final VsyncEventData mVsyncEventData = new VsyncEventData(); - private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver, - WeakReference<VsyncEventData> vsyncEventData, MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle); private static native long nativeGetDisplayEventReceiverFinalizer(); @FastNative @@ -127,9 +124,7 @@ public abstract class DisplayEventReceiver { } mMessageQueue = looper.getQueue(); - mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), - new WeakReference<VsyncEventData>(mVsyncEventData), - mMessageQueue, + mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource, eventRegistration, layerHandle); mFreeNativeResources = sNativeAllocationRegistry.registerNativeAllocation(this, mReceiverPtr); @@ -152,6 +147,9 @@ public abstract class DisplayEventReceiver { * @hide */ public static final class VsyncEventData { + static final FrameTimeline[] INVALID_FRAME_TIMELINES = + {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)}; + // The amount of frame timeline choices. // Must be in sync with VsyncEventData::kFrameTimelinesLength in // frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime @@ -159,32 +157,22 @@ public abstract class DisplayEventReceiver { static final int FRAME_TIMELINES_LENGTH = 7; public static class FrameTimeline { - FrameTimeline() {} - - // Called from native code. - @SuppressWarnings("unused") FrameTimeline(long vsyncId, long expectedPresentationTime, long deadline) { this.vsyncId = vsyncId; this.expectedPresentationTime = expectedPresentationTime; this.deadline = deadline; } - void copyFrom(FrameTimeline other) { - vsyncId = other.vsyncId; - expectedPresentationTime = other.expectedPresentationTime; - deadline = other.deadline; - } - // The frame timeline vsync id, used to correlate a frame // produced by HWUI with the timeline data stored in Surface Flinger. - public long vsyncId = FrameInfo.INVALID_VSYNC_ID; + public final long vsyncId; // The frame timestamp for when the frame is expected to be presented. - public long expectedPresentationTime = Long.MAX_VALUE; + public final long expectedPresentationTime; // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is // allotted for the frame to be completed. - public long deadline = Long.MAX_VALUE; + public final long deadline; } /** @@ -192,18 +180,11 @@ public abstract class DisplayEventReceiver { * {@link FrameInfo#VSYNC} to the current vsync in case Choreographer callback was heavily * delayed by the app. */ - public long frameInterval = -1; + public final long frameInterval; public final FrameTimeline[] frameTimelines; - public int preferredFrameTimelineIndex = 0; - - VsyncEventData() { - frameTimelines = new FrameTimeline[FRAME_TIMELINES_LENGTH]; - for (int i = 0; i < frameTimelines.length; i++) { - frameTimelines[i] = new FrameTimeline(); - } - } + public final int preferredFrameTimelineIndex; // Called from native code. @SuppressWarnings("unused") @@ -214,12 +195,10 @@ public abstract class DisplayEventReceiver { this.frameInterval = frameInterval; } - void copyFrom(VsyncEventData other) { - preferredFrameTimelineIndex = other.preferredFrameTimelineIndex; - frameInterval = other.frameInterval; - for (int i = 0; i < frameTimelines.length; i++) { - frameTimelines[i].copyFrom(other.frameTimelines[i]); - } + VsyncEventData() { + this.frameInterval = -1; + this.frameTimelines = INVALID_FRAME_TIMELINES; + this.preferredFrameTimelineIndex = 0; } public FrameTimeline preferredFrameTimeline() { @@ -325,8 +304,9 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") - private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) { - onVsync(timestampNanos, physicalDisplayId, frame, mVsyncEventData); + private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame, + VsyncEventData vsyncEventData) { + onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData); } // Called from native code. diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index 0b4adaeb9890..a8e68b71f5cc 100644 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -100,6 +101,7 @@ public abstract class InputEvent implements Parcelable { * @return The display id associated with the event. * @hide */ + @TestApi public abstract int getDisplayId(); /** @@ -107,6 +109,7 @@ public abstract class InputEvent implements Parcelable { * @param displayId * @hide */ + @TestApi public abstract void setDisplayId(int displayId); /** * Copies the event. diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index d35aff9a72b7..3812d37a5fed 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -215,6 +215,7 @@ public final class InputWindowHandle { .append(", scaleFactor=").append(scaleFactor) .append(", transform=").append(transform) .append(", windowToken=").append(windowToken) + .append(", displayId=").append(displayId) .append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0) .toString(); diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index b6d9400fad5c..858da554c670 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -2100,6 +2100,7 @@ public class KeyEvent extends InputEvent implements Parcelable { } /** @hide */ + @TestApi @Override public final int getDisplayId() { return mDisplayId; diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index ac50d09cc091..cd89a561074c 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -99,9 +99,16 @@ public class SurfaceControlViewHost { @Override public ISurfaceSyncGroup getSurfaceSyncGroup() { CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>(); - mViewRoot.mHandler.post( - () -> surfaceSyncGroup.complete( - mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup)); + // If the call came from in process and it's already running on the UI thread, return + // results immediately instead of posting to the main thread. If we post to the main + // thread, it will block itself and the return value will always be null. + if (Thread.currentThread() == mViewRoot.mThread) { + return mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup; + } else { + mViewRoot.mHandler.post( + () -> surfaceSyncGroup.complete( + mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup)); + } try { return surfaceSyncGroup.get(1, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2f5cd5434b89..055b5cb70562 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6946,6 +6946,7 @@ public final class ViewRootImpl implements ViewParent, return; } final boolean needsStylusPointerIcon = event.isStylusPointer() + && event.isHoverEvent() && mInputManager.isStylusPointerIconEnabled(); if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) { if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 088065d2f77d..7931d1a06d39 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -8095,12 +8095,14 @@ public class Editor { private boolean mIsInsertModeActive; private InsertModeTransformationMethod mInsertModeTransformationMethod; private final Paint mHighlightPaint; + private final Path mHighlightPath; InsertModeController(@NonNull TextView textView) { mTextView = Objects.requireNonNull(textView); mIsInsertModeActive = false; mInsertModeTransformationMethod = null; mHighlightPaint = new Paint(); + mHighlightPath = new Path(); // The highlight color is supposed to be 12% of the color primary40. We can't // directly access Material 3 theme. But because Material 3 sets the colorPrimary to @@ -8168,10 +8170,8 @@ public class Editor { ((InsertModeTransformationMethod.TransformedText) transformedText); final int highlightStart = insertModeTransformedText.getHighlightStart(); final int highlightEnd = insertModeTransformedText.getHighlightEnd(); - final Layout.SelectionRectangleConsumer consumer = - (left, top, right, bottom, textSelectionLayout) -> - canvas.drawRect(left, top, right, bottom, mHighlightPaint); - layout.getSelection(highlightStart, highlightEnd, consumer); + layout.getSelectionPath(highlightStart, highlightEnd, mHighlightPath); + canvas.drawPath(mHighlightPath, mHighlightPaint); } } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 18874f768929..3165654d806d 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1780,6 +1780,21 @@ public class RemoteViews implements Parcelable, Filter { Object value = getParameterValue(view); try { MethodHandle method = getMethod(view, this.methodName, param, true /* async */); + // Upload the bitmap to GPU if the parameter is of type Bitmap or Icon. + // Since bitmaps in framework are seldomly modified, this is supposed to accelerate + // the operations. + if (value instanceof Bitmap bitmap) { + bitmap.prepareToDraw(); + } + + if (value instanceof Icon icon + && (icon.getType() == Icon.TYPE_BITMAP + || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) { + Bitmap bitmap = icon.getBitmap(); + if (bitmap != null) { + bitmap.prepareToDraw(); + } + } if (method != null) { Runnable endAction = (Runnable) method.invoke(view, value); diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java index 8012a1c26bac..c47572313eeb 100644 --- a/core/java/android/window/BackMotionEvent.java +++ b/core/java/android/window/BackMotionEvent.java @@ -34,6 +34,8 @@ public final class BackMotionEvent implements Parcelable { private final float mTouchX; private final float mTouchY; private final float mProgress; + private final float mVelocityX; + private final float mVelocityY; @BackEvent.SwipeEdge private final int mSwipeEdge; @@ -43,19 +45,32 @@ public final class BackMotionEvent implements Parcelable { /** * Creates a new {@link BackMotionEvent} instance. * + * <p>Note: Velocity is only computed for last event, for performance reasons.</p> + * * @param touchX Absolute X location of the touch point of this event. * @param touchY Absolute Y location of the touch point of this event. * @param progress Value between 0 and 1 on how far along the back gesture is. + * @param velocityX X velocity computed from the touch point of this event. + * Value in pixels/second. {@link Float#NaN} if was not computed. + * @param velocityY Y velocity computed from the touch point of this event. + * Value in pixels/second. {@link Float#NaN} if was not computed. * @param swipeEdge Indicates which edge the swipe starts from. * @param departingAnimationTarget The remote animation target of the departing * application window. */ - public BackMotionEvent(float touchX, float touchY, float progress, + public BackMotionEvent( + float touchX, + float touchY, + float progress, + float velocityX, + float velocityY, @BackEvent.SwipeEdge int swipeEdge, @Nullable RemoteAnimationTarget departingAnimationTarget) { mTouchX = touchX; mTouchY = touchY; mProgress = progress; + mVelocityX = velocityX; + mVelocityY = velocityY; mSwipeEdge = swipeEdge; mDepartingAnimationTarget = departingAnimationTarget; } @@ -64,6 +79,8 @@ public final class BackMotionEvent implements Parcelable { mTouchX = in.readFloat(); mTouchY = in.readFloat(); mProgress = in.readFloat(); + mVelocityX = in.readFloat(); + mVelocityY = in.readFloat(); mSwipeEdge = in.readInt(); mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); } @@ -91,11 +108,27 @@ public final class BackMotionEvent implements Parcelable { dest.writeFloat(mTouchX); dest.writeFloat(mTouchY); dest.writeFloat(mProgress); + dest.writeFloat(mVelocityX); + dest.writeFloat(mVelocityY); dest.writeInt(mSwipeEdge); dest.writeTypedObject(mDepartingAnimationTarget, flags); } /** + * Returns the absolute X location of the touch point. + */ + public float getTouchX() { + return mTouchX; + } + + /** + * Returns the absolute Y location of the touch point. + */ + public float getTouchY() { + return mTouchY; + } + + /** * Returns the progress of a {@link BackEvent}. * * @see BackEvent#getProgress() @@ -106,17 +139,21 @@ public final class BackMotionEvent implements Parcelable { } /** - * Returns the absolute X location of the touch point. + * Returns the X velocity computed from the touch point. + * + * @return value in pixels/second or {@link Float#NaN} if was not computed. */ - public float getTouchX() { - return mTouchX; + public float getVelocityX() { + return mVelocityX; } /** - * Returns the absolute Y location of the touch point. + * Returns the Y velocity computed from the touch point. + * + * @return value in pixels/second or {@link Float#NaN} if was not computed. */ - public float getTouchY() { - return mTouchY; + public float getVelocityY() { + return mVelocityY; } /** @@ -143,6 +180,8 @@ public final class BackMotionEvent implements Parcelable { + "mTouchX=" + mTouchX + ", mTouchY=" + mTouchY + ", mProgress=" + mProgress + + ", mVelocityX=" + mVelocityX + + ", mVelocityY=" + mVelocityY + ", mSwipeEdge" + mSwipeEdge + ", mDepartingAnimationTarget" + mDepartingAnimationTarget + "}"; diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java deleted file mode 100644 index 4a46d91efbf0..000000000000 --- a/core/java/com/android/internal/expresslog/Counter.java +++ /dev/null @@ -1,71 +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.internal.expresslog; - -import android.annotation.NonNull; - -import com.android.internal.util.FrameworkStatsLog; - -/** Counter encapsulates StatsD write API calls */ -public final class Counter { - - // Not instantiable. - private Counter() {} - - /** - * Increments Telemetry Express Counter metric by 1 - * @param metricId to log, no-op if metricId is not defined in the TeX catalog - * @hide - */ - public static void logIncrement(@NonNull String metricId) { - logIncrement(metricId, 1); - } - - /** - * Increments Telemetry Express Counter metric by 1 - * @param metricId to log, no-op if metricId is not defined in the TeX catalog - * @param uid used as a dimension for the count metric - * @hide - */ - public static void logIncrementWithUid(@NonNull String metricId, int uid) { - logIncrementWithUid(metricId, uid, 1); - } - - /** - * Increments Telemetry Express Counter metric by arbitrary value - * @param metricId to log, no-op if metricId is not defined in the TeX catalog - * @param amount to increment counter - * @hide - */ - public static void logIncrement(@NonNull String metricId, long amount) { - final long metricIdHash = Utils.hashString(metricId); - FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount); - } - - /** - * Increments Telemetry Express Counter metric by arbitrary value - * @param metricId to log, no-op if metricId is not defined in the TeX catalog - * @param uid used as a dimension for the count metric - * @param amount to increment counter - * @hide - */ - public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) { - final long metricIdHash = Utils.hashString(metricId); - FrameworkStatsLog.write( - FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid); - } -} diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java deleted file mode 100644 index 2fe784a5a855..000000000000 --- a/core/java/com/android/internal/expresslog/Histogram.java +++ /dev/null @@ -1,234 +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.internal.expresslog; - -import android.annotation.FloatRange; -import android.annotation.IntRange; -import android.annotation.NonNull; - -import com.android.internal.util.FrameworkStatsLog; - -import java.util.Arrays; - -/** Histogram encapsulates StatsD write API calls */ -public final class Histogram { - - private final long mMetricIdHash; - private final BinOptions mBinOptions; - - /** - * Creates Histogram metric logging wrapper - * - * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog - * @param binOptions to calculate bin index for samples - * @hide - */ - public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) { - mMetricIdHash = Utils.hashString(metricId); - mBinOptions = binOptions; - } - - /** - * Logs increment sample count for automatically calculated bin - * - * @param sample value - * @hide - */ - public void logSample(float sample) { - final int binIndex = mBinOptions.getBinForSample(sample); - FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, - /*count*/ 1, binIndex); - } - - /** - * Logs increment sample count for automatically calculated bin - * - * @param uid used as a dimension for the count metric - * @param sample value - * @hide - */ - public void logSampleWithUid(int uid, float sample) { - final int binIndex = mBinOptions.getBinForSample(sample); - FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED, - mMetricIdHash, /*count*/ 1, binIndex, uid); - } - - /** Used by Histogram to map data sample to corresponding bin */ - public interface BinOptions { - /** - * Returns bins count to be used by a histogram - * - * @return bins count used to initialize Options, including overflow & underflow bins - * @hide - */ - int getBinsCount(); - - /** - * Returns bin index for the input sample value - * index == 0 stands for underflow - * index == getBinsCount() - 1 stands for overflow - * - * @return zero based index - * @hide - */ - int getBinForSample(float sample); - } - - /** Used by Histogram to map data sample to corresponding bin for uniform bins */ - public static final class UniformOptions implements BinOptions { - - private final int mBinCount; - private final float mMinValue; - private final float mExclusiveMaxValue; - private final float mBinSize; - - /** - * Creates options for uniform (linear) sized bins - * - * @param binCount amount of histogram bins. 2 bin indexes will be calculated - * automatically to represent underflow & overflow bins - * @param minValue is included in the first bin, values less than minValue - * go to underflow bin - * @param exclusiveMaxValue is included in the overflow bucket. For accurate - * measure up to kMax, then exclusiveMaxValue - * should be set to kMax + 1 - * @hide - */ - public UniformOptions(@IntRange(from = 1) int binCount, float minValue, - float exclusiveMaxValue) { - if (binCount < 1) { - throw new IllegalArgumentException("Bin count should be positive number"); - } - - if (exclusiveMaxValue <= minValue) { - throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)"); - } - - mMinValue = minValue; - mExclusiveMaxValue = exclusiveMaxValue; - mBinSize = (mExclusiveMaxValue - minValue) / binCount; - - // Implicitly add 2 for the extra underflow & overflow bins - mBinCount = binCount + 2; - } - - @Override - public int getBinsCount() { - return mBinCount; - } - - @Override - public int getBinForSample(float sample) { - if (sample < mMinValue) { - // goes to underflow - return 0; - } else if (sample >= mExclusiveMaxValue) { - // goes to overflow - return mBinCount - 1; - } - return (int) ((sample - mMinValue) / mBinSize + 1); - } - } - - /** Used by Histogram to map data sample to corresponding bin for scaled bins */ - public static final class ScaledRangeOptions implements BinOptions { - // store minimum value per bin - final long[] mBins; - - /** - * Creates options for scaled range bins - * - * @param binCount amount of histogram bins. 2 bin indexes will be calculated - * automatically to represent underflow & overflow bins - * @param minValue is included in the first bin, values less than minValue - * go to underflow bin - * @param firstBinWidth used to represent first bin width and as a reference to calculate - * width for consecutive bins - * @param scaleFactor used to calculate width for consecutive bins - * @hide - */ - public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue, - @FloatRange(from = 1.f) float firstBinWidth, - @FloatRange(from = 1.f) float scaleFactor) { - if (binCount < 1) { - throw new IllegalArgumentException("Bin count should be positive number"); - } - - if (firstBinWidth < 1.f) { - throw new IllegalArgumentException( - "First bin width invalid (should be 1.f at minimum)"); - } - - if (scaleFactor < 1.f) { - throw new IllegalArgumentException( - "Scaled factor invalid (should be 1.f at minimum)"); - } - - // precalculating bins ranges (no need to create a bin for underflow reference value) - mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor); - } - - @Override - public int getBinsCount() { - return mBins.length + 1; - } - - @Override - public int getBinForSample(float sample) { - if (sample < mBins[0]) { - // goes to underflow - return 0; - } else if (sample >= mBins[mBins.length - 1]) { - // goes to overflow - return mBins.length; - } - - return lower_bound(mBins, (long) sample) + 1; - } - - // To find lower bound using binary search implementation of Arrays utility class - private static int lower_bound(long[] array, long sample) { - int index = Arrays.binarySearch(array, sample); - // If key is not present in the array - if (index < 0) { - // Index specify the position of the key when inserted in the sorted array - // so the element currently present at this position will be the lower bound - return Math.abs(index) - 2; - } - return index; - } - - private static long[] initBins(int count, int minValue, float firstBinWidth, - float scaleFactor) { - long[] bins = new long[count]; - bins[0] = minValue; - double lastWidth = firstBinWidth; - for (int i = 1; i < count; i++) { - // current bin minValue = previous bin width * scaleFactor - double currentBinMinValue = bins[i - 1] + lastWidth; - if (currentBinMinValue > Integer.MAX_VALUE) { - throw new IllegalArgumentException( - "Attempted to create a bucket larger than maxint"); - } - - bins[i] = (long) currentBinMinValue; - lastWidth *= scaleFactor; - } - return bins; - } - } -} diff --git a/core/java/com/android/internal/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS deleted file mode 100644 index ee865b1e4ec8..000000000000 --- a/core/java/com/android/internal/expresslog/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/stats/OWNERS diff --git a/core/java/com/android/internal/expresslog/TEST_MAPPING b/core/java/com/android/internal/expresslog/TEST_MAPPING deleted file mode 100644 index c9b0cf80a1e6..000000000000 --- a/core/java/com/android/internal/expresslog/TEST_MAPPING +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presubmit": [ - { - "name": "ExpressLogTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - } - ] - } - ] -}
\ No newline at end of file diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java index e2c096c11fee..aa6b1c00d186 100644 --- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java +++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java @@ -20,7 +20,7 @@ import android.annotation.Nullable; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.expresslog.Counter; +import com.android.modules.expresslog.Counter; import java.io.IOException; import java.util.Arrays; diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java index f6d80a572c75..8fe2b9cdf1e5 100644 --- a/core/java/com/android/internal/util/DumpUtils.java +++ b/core/java/com/android/internal/util/DumpUtils.java @@ -25,6 +25,7 @@ import android.os.Binder; import android.os.Handler; import android.text.TextUtils; import android.util.Slog; +import android.util.SparseArray; import java.io.PrintWriter; import java.io.StringWriter; @@ -312,5 +313,85 @@ public final class DumpUtils { || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase()); }; } -} + /** + * Lambda used to dump a key (and its index) while iterating though a collection. + */ + public interface KeyDumper { + + /** Dumps the index and key.*/ + void dump(int index, int key); + } + + /** + * Lambda used to dump a value while iterating though a collection. + * + * @param <T> type of the value. + */ + public interface ValueDumper<T> { + + /** Dumps the value.*/ + void dump(T value); + } + + /** + * Dumps a sparse array. + */ + public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, + String name) { + dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null); + } + + /** + * Dumps the values of a sparse array. + */ + public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix, + SparseArray<T> array, String name) { + dumpSparseArray(pw, prefix, array, name, (i, k) -> { + pw.printf("%s%s", prefix, prefix); + }, /* valueDumper= */ null); + } + + /** + * Dumps a sparse array, customizing each line. + */ + public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array, + String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) { + int size = array.size(); + if (size == 0) { + pw.print(prefix); + pw.print("No "); + pw.print(name); + pw.println("s"); + return; + } + pw.print(prefix); + pw.print(size); + pw.print(' '); + pw.print(name); + pw.println("(s):"); + + String prefix2 = prefix + prefix; + for (int i = 0; i < size; i++) { + int key = array.keyAt(i); + T value = array.valueAt(i); + if (keyDumper != null) { + keyDumper.dump(i, key); + } else { + pw.print(prefix2); + pw.print(i); + pw.print(": "); + pw.print(key); + pw.print("->"); + } + if (value == null) { + pw.print("(null)"); + } else if (valueDumper != null) { + valueDumper.dump(value); + } else { + pw.print(value); + } + pw.println(); + } + } +} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index fc26766d7090..6bec6bc236bd 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -225,7 +225,6 @@ cc_library_shared { "android_security_Scrypt.cpp", "com_android_internal_content_om_OverlayConfig.cpp", "com_android_internal_content_om_OverlayManagerImpl.cpp", - "com_android_internal_expresslog_Utils.cpp", "com_android_internal_net_NetworkUtilsInternal.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", "com_android_internal_os_FuseAppLoop.cpp", @@ -262,6 +261,7 @@ cc_library_shared { "libstatssocket_lazy", "libskia", "libtextclassifier_hash_static", + "libexpresslog_jni", ], shared_libs: [ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index b550f28e0934..21bdf099f18d 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -200,7 +200,7 @@ extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env); -extern int register_com_android_internal_expresslog_Utils(JNIEnv* env); +extern int register_com_android_modules_expresslog_Utils(JNIEnv* env); extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); @@ -1586,7 +1586,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_incremental_IncrementalManager), REG_JNI(register_com_android_internal_content_om_OverlayConfig), REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl), - REG_JNI(register_com_android_internal_expresslog_Utils), + REG_JNI(register_com_android_modules_expresslog_Utils), REG_JNI(register_com_android_internal_net_NetworkUtilsInternal), REG_JNI(register_com_android_internal_os_ClassLoaderFactory), REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter), diff --git a/core/jni/OWNERS b/core/jni/OWNERS index bce533281186..4e4abec88040 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -104,6 +104,3 @@ per-file com_android_internal_os_*MultiStateCounter* = file:/BATTERY_STATS_OWNER # PM per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS - -# Stats/expresslog -per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 8ba4eed8b34d..e1be0cd80bb6 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2481,36 +2481,31 @@ static jint android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject th if (jSurroundFormats == nullptr) { ALOGE("jSurroundFormats is NULL"); - return (jint)AUDIO_JAVA_BAD_VALUE; + return static_cast<jint>(AUDIO_JAVA_BAD_VALUE); } if (!env->IsInstanceOf(jSurroundFormats, gMapClass)) { ALOGE("getSurroundFormats not a map"); - return (jint)AUDIO_JAVA_BAD_VALUE; + return static_cast<jint>(AUDIO_JAVA_BAD_VALUE); } jint jStatus; unsigned int numSurroundFormats = 0; - audio_format_t *surroundFormats = nullptr; - bool *surroundFormatsEnabled = nullptr; - status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats, - surroundFormatsEnabled); + status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, nullptr, nullptr); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status); - jStatus = nativeToJavaStatus(status); - goto exit; + return nativeToJavaStatus(status); } if (numSurroundFormats == 0) { - jStatus = (jint)AUDIO_JAVA_SUCCESS; - goto exit; + return static_cast<jint>(AUDIO_JAVA_SUCCESS); } - surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t)); - surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool)); - status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats, - surroundFormatsEnabled); + auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats); + auto surroundFormatsEnabled = std::make_unique<bool[]>(numSurroundFormats); + status = AudioSystem::getSurroundFormats(&numSurroundFormats, &surroundFormats[0], + &surroundFormatsEnabled[0]); jStatus = nativeToJavaStatus(status); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status); - goto exit; + return jStatus; } for (size_t i = 0; i < numSurroundFormats; i++) { int audioFormat = audioFormatFromNative(surroundFormats[i]); @@ -2526,9 +2521,6 @@ static jint android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject th env->DeleteLocalRef(enabled); } -exit: - free(surroundFormats); - free(surroundFormatsEnabled); return jStatus; } @@ -2538,31 +2530,28 @@ static jint android_media_AudioSystem_getReportedSurroundFormats(JNIEnv *env, jo if (jSurroundFormats == nullptr) { ALOGE("jSurroundFormats is NULL"); - return (jint)AUDIO_JAVA_BAD_VALUE; + return static_cast<jint>(AUDIO_JAVA_BAD_VALUE); } if (!env->IsInstanceOf(jSurroundFormats, gArrayListClass)) { ALOGE("jSurroundFormats not an arraylist"); - return (jint)AUDIO_JAVA_BAD_VALUE; + return static_cast<jint>(AUDIO_JAVA_BAD_VALUE); } jint jStatus; unsigned int numSurroundFormats = 0; - audio_format_t *surroundFormats = nullptr; - status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats); + status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, nullptr); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status); - jStatus = nativeToJavaStatus(status); - goto exit; + return nativeToJavaStatus(status); } if (numSurroundFormats == 0) { - jStatus = (jint)AUDIO_JAVA_SUCCESS; - goto exit; + return static_cast<jint>(AUDIO_JAVA_SUCCESS); } - surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t)); - status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats); + auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats); + status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, &surroundFormats[0]); jStatus = nativeToJavaStatus(status); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status); - goto exit; + return jStatus; } for (size_t i = 0; i < numSurroundFormats; i++) { int audioFormat = audioFormatFromNative(surroundFormats[i]); @@ -2576,8 +2565,6 @@ static jint android_media_AudioSystem_getReportedSurroundFormats(JNIEnv *env, jo env->DeleteLocalRef(surroundFormat); } -exit: - free(surroundFormats); return jStatus; } diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index 410b44161cf6..dd72689206ba 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -48,22 +48,12 @@ static struct { struct { jclass clazz; - jmethodID init; - - jfieldID vsyncId; - jfieldID expectedPresentationTime; - jfieldID deadline; } frameTimelineClassInfo; struct { jclass clazz; - jmethodID init; - - jfieldID frameInterval; - jfieldID preferredFrameTimelineIndex; - jfieldID frameTimelines; } vsyncEventDataClassInfo; } gDisplayEventReceiverClassInfo; @@ -71,7 +61,7 @@ static struct { class NativeDisplayEventReceiver : public DisplayEventDispatcher { public: - NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, jobject vsyncEventDataWeak, + NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource, jint eventRegistration, jlong layerHandle); @@ -82,7 +72,6 @@ protected: private: jobject mReceiverWeakGlobal; - jobject mVsyncEventDataWeakGlobal; sp<MessageQueue> mMessageQueue; void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, @@ -96,7 +85,6 @@ private: }; NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, - jobject vsyncEventDataWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource, jint eventRegistration, jlong layerHandle) @@ -108,7 +96,6 @@ NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject rece reinterpret_cast<IBinder*>(layerHandle)) : nullptr), mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), - mVsyncEventDataWeakGlobal(env->NewGlobalRef(vsyncEventDataWeak)), mMessageQueue(messageQueue) { ALOGV("receiver %p ~ Initializing display event receiver.", this); } @@ -167,43 +154,12 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla JNIEnv* env = AndroidRuntime::getJNIEnv(); ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal)); - ScopedLocalRef<jobject> vsyncEventDataObj(env, GetReferent(env, mVsyncEventDataWeakGlobal)); - if (receiverObj.get() && vsyncEventDataObj.get()) { + if (receiverObj.get()) { ALOGV("receiver %p ~ Invoking vsync handler.", this); - env->SetIntField(vsyncEventDataObj.get(), - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo - .preferredFrameTimelineIndex, - vsyncEventData.preferredFrameTimelineIndex); - env->SetLongField(vsyncEventDataObj.get(), - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval, - vsyncEventData.frameInterval); - - ScopedLocalRef<jobjectArray> - frameTimelinesObj(env, - reinterpret_cast<jobjectArray>( - env->GetObjectField(vsyncEventDataObj.get(), - gDisplayEventReceiverClassInfo - .vsyncEventDataClassInfo - .frameTimelines))); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { - VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i]; - ScopedLocalRef<jobject> - frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i)); - env->SetLongField(frameTimelineObj.get(), - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId, - frameTimeline.vsyncId); - env->SetLongField(frameTimelineObj.get(), - gDisplayEventReceiverClassInfo.frameTimelineClassInfo - .expectedPresentationTime, - frameTimeline.expectedPresentationTime); - env->SetLongField(frameTimelineObj.get(), - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline, - frameTimeline.deadlineTimestamp); - } - + jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData); env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync, - timestamp, displayId.value, count); + timestamp, displayId.value, count, javaVsyncEventData); ALOGV("receiver %p ~ Returned from vsync handler.", this); } @@ -271,9 +227,8 @@ void NativeDisplayEventReceiver::dispatchFrameRateOverrides( mMessageQueue->raiseAndClearException(env, "dispatchModeChanged"); } -static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak, - jobject messageQueueObj, jint vsyncSource, jint eventRegistration, - jlong layerHandle) { +static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj, + jint vsyncSource, jint eventRegistration, jlong layerHandle) { sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); if (messageQueue == NULL) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); @@ -281,8 +236,8 @@ static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject } sp<NativeDisplayEventReceiver> receiver = - new NativeDisplayEventReceiver(env, receiverWeak, vsyncEventDataWeak, messageQueue, - vsyncSource, eventRegistration, layerHandle); + new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource, + eventRegistration, layerHandle); status_t status = receiver->initialize(); if (status) { String8 message; @@ -329,9 +284,7 @@ static jobject nativeGetLatestVsyncEventData(JNIEnv* env, jclass clazz, jlong re static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - {"nativeInit", - "(Ljava/lang/ref/WeakReference;Ljava/lang/ref/WeakReference;Landroid/os/" - "MessageQueue;IIJ)J", + {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J", (void*)nativeInit}, {"nativeGetDisplayEventReceiverFinalizer", "()J", (void*)nativeGetDisplayEventReceiverFinalizer}, @@ -348,7 +301,8 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); gDisplayEventReceiverClassInfo.dispatchVsync = - GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V"); + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", + "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V"); gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V"); gDisplayEventReceiverClassInfo.dispatchModeChanged = @@ -374,15 +328,6 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, "<init>", "(JJJ)V"); - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, - "vsyncId", "J"); - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.expectedPresentationTime = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, - "expectedPresentationTime", "J"); - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, - "deadline", "J"); jclass vsyncEventDataClazz = FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData"); @@ -394,17 +339,6 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { "([Landroid/view/" "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V"); - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, - "preferredFrameTimelineIndex", "I"); - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, - "frameInterval", "J"); - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameTimelines = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, - "frameTimelines", - "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;"); - return res; } diff --git a/core/jni/com_android_internal_expresslog_Utils.cpp b/core/jni/com_android_internal_expresslog_Utils.cpp deleted file mode 100644 index d33a7bda27f7..000000000000 --- a/core/jni/com_android_internal_expresslog_Utils.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <nativehelper/JNIHelp.h> -#include <utils/hash/farmhash.h> - -#include "core_jni_helpers.h" - -// ---------------------------------------------------------------------------- -// JNI Glue -// ---------------------------------------------------------------------------- - -static jclass g_stringClass = nullptr; - -/** - * Class: com_android_internal_expresslog_Utils - * Method: hashString - * Signature: (Ljava/lang/String;)J - */ -static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) { - ScopedUtfChars name(env, metricNameObj); - if (name.c_str() == nullptr) { - return 0; - } - - return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size())); -} - -static const JNINativeMethod g_methods[] = { - {"hashString", "(Ljava/lang/String;)J", (void*)hashString}, -}; - -static const char* const kUtilsPathName = "com/android/internal/expresslog/Utils"; - -namespace android { - -int register_com_android_internal_expresslog_Utils(JNIEnv* env) { - jclass stringClass = FindClassOrDie(env, "java/lang/String"); - g_stringClass = MakeGlobalRefOrDie(env, stringClass); - - return RegisterMethodsOrDie(env, kUtilsPathName, g_methods, NELEM(g_methods)); -} - -} // namespace android diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index ed612a05e6b5..025a57d0e334 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -1035,6 +1035,7 @@ message AppsExitInfoProto { optional int32 uid = 1; repeated .android.app.ApplicationExitInfoProto app_exit_info = 2; + repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3; } repeated User users = 2; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 78d39236b392..05b38a562e29 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1746,37 +1746,6 @@ android:protectionLevel="dangerous" android:permissionFlags="hardRestricted" /> - <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors. - <p class="note"><strong>Note: </strong> This permission is for Wear OS only. - <p>Protection level: dangerous - @hide - --> - <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" - android:permissionGroup="android.permission-group.UNDEFINED" - android:label="@string/permlab_bodySensorsWristTemperature" - android:description="@string/permdesc_bodySensorsWristTemperature" - android:backgroundPermission="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND" - android:protectionLevel="dangerous" /> - - <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors. - If you're requesting this permission, you must also request - {@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't - give you wrist temperature body sensors access. - <p class="note"><strong>Note: </strong> This permission is for Wear OS only. - <p>Protection level: dangerous - - <p> This is a hard restricted permission which cannot be held by an app until - the installer on record allowlists the permission. For more details see - {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. - @hide - --> - <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND" - android:permissionGroup="android.permission-group.UNDEFINED" - android:label="@string/permlab_bodySensors_wristTemperature_background" - android:description="@string/permdesc_bodySensors_wristTemperature_background" - android:protectionLevel="dangerous" - android:permissionFlags="hardRestricted" /> - <!-- Allows an app to use fingerprint hardware. <p>Protection level: normal @deprecated Applications should request {@link diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e4d74b5373e0..9f10ae6066f5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1373,6 +1373,9 @@ <!-- Number of notifications to keep in the notification service historical archive --> <integer name="config_notificationServiceArchiveSize">100</integer> + <!-- List of packages that will be able to use full screen intent in notifications by default --> + <string-array name="config_useFullScreenIntentPackages" translatable="false" /> + <!-- Allow the menu hard key to be disabled in LockScreen on some devices --> <bool name="config_disableMenuKeyInLockScreen">false</bool> diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml index 85325fec7277..daa0f553f47a 100644 --- a/core/res/res/values/public-final.xml +++ b/core/res/res/values/public-final.xml @@ -3402,4 +3402,343 @@ <!-- @hide @SystemApi --> <public type="bool" name="config_enableQrCodeScannerOnLockScreen" id="0x01110008" /> + <!-- =============================================================== + Resources added in version U of the platform + + NOTE: After this version of the platform is forked, changes cannot be made to the root + branch's groups for that release. Only merge changes to the forked platform branch. + =============================================================== --> + <eat-comment/> + + <staging-public-group-final type="attr" first-id="0x01ce0000"> + <public name="handwritingBoundsOffsetLeft" /> + <public name="handwritingBoundsOffsetTop" /> + <public name="handwritingBoundsOffsetRight" /> + <public name="handwritingBoundsOffsetBottom" /> + <public name="accessibilityDataSensitive" /> + <public name="enableTextStylingShortcuts" /> + <public name="requiredDisplayCategory"/> + <public name="removed_maxConcurrentSessionsCount" /> + <public name="visualQueryDetectionService" /> + <public name="physicalKeyboardHintLanguageTag" /> + <public name="physicalKeyboardHintLayoutType" /> + <public name="allowSharedIsolatedProcess" /> + <public name="keyboardLocale" /> + <public name="keyboardLayoutType" /> + <public name="allowUpdateOwnership" /> + <public name="isCredential"/> + <public name="searchResultHighlightColor" /> + <public name="focusedSearchResultHighlightColor" /> + <public name="stylusHandwritingSettingsActivity" /> + <public name="windowNoMoveAnimation" /> + <public name="settingsSubtitle" /> + <public name="capability" /> + </staging-public-group-final> + + <public type="attr" name="handwritingBoundsOffsetLeft" id="0x01010673" /> + <public type="attr" name="handwritingBoundsOffsetTop" id="0x01010674" /> + <public type="attr" name="handwritingBoundsOffsetRight" id="0x01010675" /> + <public type="attr" name="handwritingBoundsOffsetBottom" id="0x01010676" /> + <public type="attr" name="accessibilityDataSensitive" id="0x01010677" /> + <public type="attr" name="enableTextStylingShortcuts" id="0x01010678" /> + <public type="attr" name="requiredDisplayCategory" id="0x01010679" /> + <public type="attr" name="visualQueryDetectionService" id="0x0101067a" /> + <public type="attr" name="physicalKeyboardHintLanguageTag" id="0x0101067b" /> + <public type="attr" name="physicalKeyboardHintLayoutType" id="0x0101067c" /> + <public type="attr" name="allowSharedIsolatedProcess" id="0x0101067d" /> + <public type="attr" name="keyboardLocale" id="0x0101067e" /> + <public type="attr" name="keyboardLayoutType" id="0x0101067f" /> + <public type="attr" name="allowUpdateOwnership" id="0x01010680" /> + <public type="attr" name="isCredential" id="0x01010681" /> + <public type="attr" name="searchResultHighlightColor" id="0x01010682" /> + <public type="attr" name="focusedSearchResultHighlightColor" id="0x01010683" /> + <public type="attr" name="stylusHandwritingSettingsActivity" id="0x01010684" /> + <public type="attr" name="windowNoMoveAnimation" id="0x01010685" /> + <public type="attr" name="settingsSubtitle" id="0x01010686" /> + <public type="attr" name="capability" id="0x01010687" /> + + <staging-public-group-final type="id" first-id="0x01cd0000"> + <public name="bold" /> + <public name="italic" /> + <public name="underline" /> + <public name="accessibilityActionScrollInDirection" /> + </staging-public-group-final> + + <public type="id" name="bold" id="0x0102005b" /> + <public type="id" name="italic" id="0x0102005c" /> + <public type="id" name="underline" id="0x0102005d" /> + <public type="id" name="accessibilityActionScrollInDirection" id="0x0102005e" /> + + <staging-public-group-final type="string" first-id="0x01cb0000"> + <!-- @hide @SystemApi --> + <public name="config_systemWearHealthService" /> + <!-- @hide @SystemApi --> + <public name="config_defaultNotes" /> + <!-- @hide @SystemApi --> + <public name="config_systemFinancedDeviceController" /> + <!-- @hide @SystemApi --> + <public name="config_systemCallStreaming" /> + </staging-public-group-final> + + <!-- @hide @SystemApi --> + <public type="string" name="config_systemWearHealthService" id="0x01040044" /> + <!-- @hide @SystemApi --> + <public type="string" name="config_defaultNotes" id="0x01040045" /> + <!-- @hide @SystemApi --> + <public type="string" name="config_systemFinancedDeviceController" id="0x01040046" /> + <!-- @hide @SystemApi --> + <public type="string" name="config_systemCallStreaming" id="0x01040047" /> + + <staging-public-group-final type="dimen" first-id="0x01ca0000"> + <!-- @hide @SystemApi --> + <public name="config_viewConfigurationHandwritingGestureLineMargin" /> + </staging-public-group-final> + + <!-- @hide @SystemApi --> + <public type="dimen" name="config_viewConfigurationHandwritingGestureLineMargin" id="0x0105000a" /> + + <staging-public-group-final type="color" first-id="0x01c90000"> + <public name="system_primary_container_light" /> + <public name="system_on_primary_container_light" /> + <public name="system_primary_light" /> + <public name="system_on_primary_light" /> + <public name="system_secondary_container_light" /> + <public name="system_on_secondary_container_light" /> + <public name="system_secondary_light" /> + <public name="system_on_secondary_light" /> + <public name="system_tertiary_container_light" /> + <public name="system_on_tertiary_container_light" /> + <public name="system_tertiary_light" /> + <public name="system_on_tertiary_light" /> + <public name="system_background_light" /> + <public name="system_on_background_light" /> + <public name="system_surface_light" /> + <public name="system_on_surface_light" /> + <public name="system_surface_container_low_light" /> + <public name="system_surface_container_lowest_light" /> + <public name="system_surface_container_light" /> + <public name="system_surface_container_high_light" /> + <public name="system_surface_container_highest_light" /> + <public name="system_surface_bright_light" /> + <public name="system_surface_dim_light" /> + <public name="system_surface_variant_light" /> + <public name="system_on_surface_variant_light" /> + <public name="system_outline_light" /> + <public name="system_error_light" /> + <public name="system_on_error_light" /> + <public name="system_error_container_light" /> + <public name="system_on_error_container_light" /> + <public name="removed_system_primary_fixed_light" /> + <public name="removed_system_primary_fixed_dim_light" /> + <public name="removed_system_on_primary_fixed_light" /> + <public name="removed_system_on_primary_fixed_variant_light" /> + <public name="removed_system_secondary_fixed_light" /> + <public name="removed_system_secondary_fixed_dim_light" /> + <public name="removed_system_on_secondary_fixed_light" /> + <public name="removed_system_on_secondary_fixed_variant_light" /> + <public name="removed_system_tertiary_fixed_light" /> + <public name="removed_system_tertiary_fixed_dim_light" /> + <public name="removed_system_on_tertiary_fixed_light" /> + <public name="removed_system_on_tertiary_fixed_variant_light" /> + <public name="system_control_activated_light" /> + <public name="system_control_normal_light" /> + <public name="system_control_highlight_light" /> + <public name="system_text_primary_inverse_light" /> + <public name="system_text_secondary_and_tertiary_inverse_light" /> + <public name="system_text_primary_inverse_disable_only_light" /> + <public name="system_text_secondary_and_tertiary_inverse_disabled_light" /> + <public name="system_text_hint_inverse_light" /> + <public name="system_palette_key_color_primary_light" /> + <public name="system_palette_key_color_secondary_light" /> + <public name="system_palette_key_color_tertiary_light" /> + <public name="system_palette_key_color_neutral_light" /> + <public name="system_palette_key_color_neutral_variant_light" /> + <public name="system_primary_container_dark"/> + <public name="system_on_primary_container_dark"/> + <public name="system_primary_dark"/> + <public name="system_on_primary_dark"/> + <public name="system_secondary_container_dark"/> + <public name="system_on_secondary_container_dark"/> + <public name="system_secondary_dark"/> + <public name="system_on_secondary_dark"/> + <public name="system_tertiary_container_dark"/> + <public name="system_on_tertiary_container_dark"/> + <public name="system_tertiary_dark"/> + <public name="system_on_tertiary_dark"/> + <public name="system_background_dark"/> + <public name="system_on_background_dark"/> + <public name="system_surface_dark"/> + <public name="system_on_surface_dark"/> + <public name="system_surface_container_low_dark"/> + <public name="system_surface_container_lowest_dark"/> + <public name="system_surface_container_dark"/> + <public name="system_surface_container_high_dark"/> + <public name="system_surface_container_highest_dark"/> + <public name="system_surface_bright_dark"/> + <public name="system_surface_dim_dark"/> + <public name="system_surface_variant_dark"/> + <public name="system_on_surface_variant_dark"/> + <public name="system_outline_dark"/> + <public name="system_error_dark"/> + <public name="system_on_error_dark"/> + <public name="system_error_container_dark"/> + <public name="system_on_error_container_dark"/> + <public name="removed_system_primary_fixed_dark"/> + <public name="removed_system_primary_fixed_dim_dark"/> + <public name="removed_system_on_primary_fixed_dark"/> + <public name="removed_system_on_primary_fixed_variant_dark"/> + <public name="removed_system_secondary_fixed_dark"/> + <public name="removed_system_secondary_fixed_dim_dark"/> + <public name="removed_system_on_secondary_fixed_dark"/> + <public name="removed_system_on_secondary_fixed_variant_dark"/> + <public name="removed_system_tertiary_fixed_dark"/> + <public name="removed_system_tertiary_fixed_dim_dark"/> + <public name="removed_system_on_tertiary_fixed_dark"/> + <public name="removed_system_on_tertiary_fixed_variant_dark"/> + <public name="system_control_activated_dark"/> + <public name="system_control_normal_dark"/> + <public name="system_control_highlight_dark"/> + <public name="system_text_primary_inverse_dark"/> + <public name="system_text_secondary_and_tertiary_inverse_dark"/> + <public name="system_text_primary_inverse_disable_only_dark"/> + <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/> + <public name="system_text_hint_inverse_dark"/> + <public name="system_palette_key_color_primary_dark"/> + <public name="system_palette_key_color_secondary_dark"/> + <public name="system_palette_key_color_tertiary_dark"/> + <public name="system_palette_key_color_neutral_dark"/> + <public name="system_palette_key_color_neutral_variant_dark"/> + <public name="system_primary_fixed" /> + <public name="system_primary_fixed_dim" /> + <public name="system_on_primary_fixed" /> + <public name="system_on_primary_fixed_variant" /> + <public name="system_secondary_fixed" /> + <public name="system_secondary_fixed_dim" /> + <public name="system_on_secondary_fixed" /> + <public name="system_on_secondary_fixed_variant" /> + <public name="system_tertiary_fixed" /> + <public name="system_tertiary_fixed_dim" /> + <public name="system_on_tertiary_fixed" /> + <public name="system_on_tertiary_fixed_variant" /> + <public name="system_outline_variant_light" /> + <public name="system_outline_variant_dark" /> + </staging-public-group-final> + + <public type="color" name="system_primary_container_light" id="0x0106005e" /> + <public type="color" name="system_on_primary_container_light" id="0x0106005f" /> + <public type="color" name="system_primary_light" id="0x01060060" /> + <public type="color" name="system_on_primary_light" id="0x01060061" /> + <public type="color" name="system_secondary_container_light" id="0x01060062" /> + <public type="color" name="system_on_secondary_container_light" id="0x01060063" /> + <public type="color" name="system_secondary_light" id="0x01060064" /> + <public type="color" name="system_on_secondary_light" id="0x01060065" /> + <public type="color" name="system_tertiary_container_light" id="0x01060066" /> + <public type="color" name="system_on_tertiary_container_light" id="0x01060067" /> + <public type="color" name="system_tertiary_light" id="0x01060068" /> + <public type="color" name="system_on_tertiary_light" id="0x01060069" /> + <public type="color" name="system_background_light" id="0x0106006a" /> + <public type="color" name="system_on_background_light" id="0x0106006b" /> + <public type="color" name="system_surface_light" id="0x0106006c" /> + <public type="color" name="system_on_surface_light" id="0x0106006d" /> + <public type="color" name="system_surface_container_low_light" id="0x0106006e" /> + <public type="color" name="system_surface_container_lowest_light" id="0x0106006f" /> + <public type="color" name="system_surface_container_light" id="0x01060070" /> + <public type="color" name="system_surface_container_high_light" id="0x01060071" /> + <public type="color" name="system_surface_container_highest_light" id="0x01060072" /> + <public type="color" name="system_surface_bright_light" id="0x01060073" /> + <public type="color" name="system_surface_dim_light" id="0x01060074" /> + <public type="color" name="system_surface_variant_light" id="0x01060075" /> + <public type="color" name="system_on_surface_variant_light" id="0x01060076" /> + <public type="color" name="system_outline_light" id="0x01060077" /> + <public type="color" name="system_error_light" id="0x01060078" /> + <public type="color" name="system_on_error_light" id="0x01060079" /> + <public type="color" name="system_error_container_light" id="0x0106007a" /> + <public type="color" name="system_on_error_container_light" id="0x0106007b" /> + <public type="color" name="system_control_activated_light" id="0x0106007c" /> + <public type="color" name="system_control_normal_light" id="0x0106007d" /> + <public type="color" name="system_control_highlight_light" id="0x0106007e" /> + <public type="color" name="system_text_primary_inverse_light" id="0x0106007f" /> + <public type="color" name="system_text_secondary_and_tertiary_inverse_light" id="0x01060080" /> + <public type="color" name="system_text_primary_inverse_disable_only_light" id="0x01060081" /> + <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_light" id="0x01060082" /> + <public type="color" name="system_text_hint_inverse_light" id="0x01060083" /> + <public type="color" name="system_palette_key_color_primary_light" id="0x01060084" /> + <public type="color" name="system_palette_key_color_secondary_light" id="0x01060085" /> + <public type="color" name="system_palette_key_color_tertiary_light" id="0x01060086" /> + <public type="color" name="system_palette_key_color_neutral_light" id="0x01060087" /> + <public type="color" name="system_palette_key_color_neutral_variant_light" id="0x01060088" /> + <public type="color" name="system_primary_container_dark" id="0x01060089" /> + <public type="color" name="system_on_primary_container_dark" id="0x0106008a" /> + <public type="color" name="system_primary_dark" id="0x0106008b" /> + <public type="color" name="system_on_primary_dark" id="0x0106008c" /> + <public type="color" name="system_secondary_container_dark" id="0x0106008d" /> + <public type="color" name="system_on_secondary_container_dark" id="0x0106008e" /> + <public type="color" name="system_secondary_dark" id="0x0106008f" /> + <public type="color" name="system_on_secondary_dark" id="0x01060090" /> + <public type="color" name="system_tertiary_container_dark" id="0x01060091" /> + <public type="color" name="system_on_tertiary_container_dark" id="0x01060092" /> + <public type="color" name="system_tertiary_dark" id="0x01060093" /> + <public type="color" name="system_on_tertiary_dark" id="0x01060094" /> + <public type="color" name="system_background_dark" id="0x01060095" /> + <public type="color" name="system_on_background_dark" id="0x01060096" /> + <public type="color" name="system_surface_dark" id="0x01060097" /> + <public type="color" name="system_on_surface_dark" id="0x01060098" /> + <public type="color" name="system_surface_container_low_dark" id="0x01060099" /> + <public type="color" name="system_surface_container_lowest_dark" id="0x0106009a" /> + <public type="color" name="system_surface_container_dark" id="0x0106009b" /> + <public type="color" name="system_surface_container_high_dark" id="0x0106009c" /> + <public type="color" name="system_surface_container_highest_dark" id="0x0106009d" /> + <public type="color" name="system_surface_bright_dark" id="0x0106009e" /> + <public type="color" name="system_surface_dim_dark" id="0x0106009f" /> + <public type="color" name="system_surface_variant_dark" id="0x010600a0" /> + <public type="color" name="system_on_surface_variant_dark" id="0x010600a1" /> + <public type="color" name="system_outline_dark" id="0x010600a2" /> + <public type="color" name="system_error_dark" id="0x010600a3" /> + <public type="color" name="system_on_error_dark" id="0x010600a4" /> + <public type="color" name="system_error_container_dark" id="0x010600a5" /> + <public type="color" name="system_on_error_container_dark" id="0x010600a6" /> + <public type="color" name="system_control_activated_dark" id="0x010600a7" /> + <public type="color" name="system_control_normal_dark" id="0x010600a8" /> + <public type="color" name="system_control_highlight_dark" id="0x010600a9" /> + <public type="color" name="system_text_primary_inverse_dark" id="0x010600aa" /> + <public type="color" name="system_text_secondary_and_tertiary_inverse_dark" id="0x010600ab" /> + <public type="color" name="system_text_primary_inverse_disable_only_dark" id="0x010600ac" /> + <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_dark" id="0x010600ad" /> + <public type="color" name="system_text_hint_inverse_dark" id="0x010600ae" /> + <public type="color" name="system_palette_key_color_primary_dark" id="0x010600af" /> + <public type="color" name="system_palette_key_color_secondary_dark" id="0x010600b0" /> + <public type="color" name="system_palette_key_color_tertiary_dark" id="0x010600b1" /> + <public type="color" name="system_palette_key_color_neutral_dark" id="0x010600b2" /> + <public type="color" name="system_palette_key_color_neutral_variant_dark" id="0x010600b3" /> + <public type="color" name="system_primary_fixed" id="0x010600b4" /> + <public type="color" name="system_primary_fixed_dim" id="0x010600b5" /> + <public type="color" name="system_on_primary_fixed" id="0x010600b6" /> + <public type="color" name="system_on_primary_fixed_variant" id="0x010600b7" /> + <public type="color" name="system_secondary_fixed" id="0x010600b8" /> + <public type="color" name="system_secondary_fixed_dim" id="0x010600b9" /> + <public type="color" name="system_on_secondary_fixed" id="0x010600ba" /> + <public type="color" name="system_on_secondary_fixed_variant" id="0x010600bb" /> + <public type="color" name="system_tertiary_fixed" id="0x010600bc" /> + <public type="color" name="system_tertiary_fixed_dim" id="0x010600bd" /> + <public type="color" name="system_on_tertiary_fixed" id="0x010600be" /> + <public type="color" name="system_on_tertiary_fixed_variant" id="0x010600bf" /> + <public type="color" name="system_outline_variant_light" id="0x010600c0" /> + <public type="color" name="system_outline_variant_dark" id="0x010600c1" /> + + <staging-public-group-final type="bool" first-id="0x01be0000"> + <!-- @hide @SystemApi --> + <public name="config_safetyProtectionEnabled" /> + <!-- @hide @SystemApi --> + <public name="config_enableDefaultNotes" /> + <!-- @hide @SystemApi --> + <public name="config_enableDefaultNotesForWorkProfile" /> + </staging-public-group-final> + + <!-- @hide @SystemApi --> + <public type="bool" name="config_safetyProtectionEnabled" id="0x01110009" /> + <!-- @hide @SystemApi --> + <public type="bool" name="config_enableDefaultNotes" id="0x0111000a" /> + <!-- @hide @SystemApi --> + <public type="bool" name="config_enableDefaultNotesForWorkProfile" id="0x0111000b" /> + </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 9cbf3b679851..49a19407e59f 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -102,231 +102,65 @@ <resources> <!-- =============================================================== - Resources added in version U of the platform + Resources added in version NEXT of the platform NOTE: After this version of the platform is forked, changes cannot be made to the root branch's groups for that release. Only merge changes to the forked platform branch. =============================================================== --> <eat-comment/> - <staging-public-group type="attr" first-id="0x01ce0000"> - <public name="handwritingBoundsOffsetLeft" /> - <public name="handwritingBoundsOffsetTop" /> - <public name="handwritingBoundsOffsetRight" /> - <public name="handwritingBoundsOffsetBottom" /> - <public name="accessibilityDataSensitive" /> - <public name="enableTextStylingShortcuts" /> - <public name="requiredDisplayCategory"/> - <public name="removed_maxConcurrentSessionsCount" /> - <public name="visualQueryDetectionService" /> - <public name="physicalKeyboardHintLanguageTag" /> - <public name="physicalKeyboardHintLayoutType" /> - <public name="allowSharedIsolatedProcess" /> - <public name="keyboardLocale" /> - <public name="keyboardLayoutType" /> - <public name="allowUpdateOwnership" /> - <public name="isCredential"/> - <public name="searchResultHighlightColor" /> - <public name="focusedSearchResultHighlightColor" /> - <public name="stylusHandwritingSettingsActivity" /> - <public name="windowNoMoveAnimation" /> - <public name="settingsSubtitle" /> - <public name="capability" /> + <staging-public-group type="attr" first-id="0x01bd0000"> </staging-public-group> - <staging-public-group type="id" first-id="0x01cd0000"> - <public name="bold" /> - <public name="italic" /> - <public name="underline" /> - <public name="accessibilityActionScrollInDirection" /> + <staging-public-group type="id" first-id="0x01bc0000"> </staging-public-group> - <staging-public-group type="style" first-id="0x01cc0000"> + <staging-public-group type="style" first-id="0x01bb0000"> </staging-public-group> - <staging-public-group type="string" first-id="0x01cb0000"> - <!-- @hide @SystemApi --> - <public name="config_systemWearHealthService" /> - <!-- @hide @SystemApi --> - <public name="config_defaultNotes" /> - <!-- @hide @SystemApi --> - <public name="config_systemFinancedDeviceController" /> - <!-- @hide @SystemApi --> - <public name="config_systemCallStreaming" /> + <staging-public-group type="string" first-id="0x01ba0000"> </staging-public-group> - <staging-public-group type="dimen" first-id="0x01ca0000"> - <!-- @hide @SystemApi --> - <public name="config_viewConfigurationHandwritingGestureLineMargin" /> + <staging-public-group type="dimen" first-id="0x01b90000"> </staging-public-group> - <staging-public-group type="color" first-id="0x01c90000"> - <public name="system_primary_container_light" /> - <public name="system_on_primary_container_light" /> - <public name="system_primary_light" /> - <public name="system_on_primary_light" /> - <public name="system_secondary_container_light" /> - <public name="system_on_secondary_container_light" /> - <public name="system_secondary_light" /> - <public name="system_on_secondary_light" /> - <public name="system_tertiary_container_light" /> - <public name="system_on_tertiary_container_light" /> - <public name="system_tertiary_light" /> - <public name="system_on_tertiary_light" /> - <public name="system_background_light" /> - <public name="system_on_background_light" /> - <public name="system_surface_light" /> - <public name="system_on_surface_light" /> - <public name="system_surface_container_low_light" /> - <public name="system_surface_container_lowest_light" /> - <public name="system_surface_container_light" /> - <public name="system_surface_container_high_light" /> - <public name="system_surface_container_highest_light" /> - <public name="system_surface_bright_light" /> - <public name="system_surface_dim_light" /> - <public name="system_surface_variant_light" /> - <public name="system_on_surface_variant_light" /> - <public name="system_outline_light" /> - <public name="system_error_light" /> - <public name="system_on_error_light" /> - <public name="system_error_container_light" /> - <public name="system_on_error_container_light" /> - <public name="removed_system_primary_fixed_light" /> - <public name="removed_system_primary_fixed_dim_light" /> - <public name="removed_system_on_primary_fixed_light" /> - <public name="removed_system_on_primary_fixed_variant_light" /> - <public name="removed_system_secondary_fixed_light" /> - <public name="removed_system_secondary_fixed_dim_light" /> - <public name="removed_system_on_secondary_fixed_light" /> - <public name="removed_system_on_secondary_fixed_variant_light" /> - <public name="removed_system_tertiary_fixed_light" /> - <public name="removed_system_tertiary_fixed_dim_light" /> - <public name="removed_system_on_tertiary_fixed_light" /> - <public name="removed_system_on_tertiary_fixed_variant_light" /> - <public name="system_control_activated_light" /> - <public name="system_control_normal_light" /> - <public name="system_control_highlight_light" /> - <public name="system_text_primary_inverse_light" /> - <public name="system_text_secondary_and_tertiary_inverse_light" /> - <public name="system_text_primary_inverse_disable_only_light" /> - <public name="system_text_secondary_and_tertiary_inverse_disabled_light" /> - <public name="system_text_hint_inverse_light" /> - <public name="system_palette_key_color_primary_light" /> - <public name="system_palette_key_color_secondary_light" /> - <public name="system_palette_key_color_tertiary_light" /> - <public name="system_palette_key_color_neutral_light" /> - <public name="system_palette_key_color_neutral_variant_light" /> - <public name="system_primary_container_dark"/> - <public name="system_on_primary_container_dark"/> - <public name="system_primary_dark"/> - <public name="system_on_primary_dark"/> - <public name="system_secondary_container_dark"/> - <public name="system_on_secondary_container_dark"/> - <public name="system_secondary_dark"/> - <public name="system_on_secondary_dark"/> - <public name="system_tertiary_container_dark"/> - <public name="system_on_tertiary_container_dark"/> - <public name="system_tertiary_dark"/> - <public name="system_on_tertiary_dark"/> - <public name="system_background_dark"/> - <public name="system_on_background_dark"/> - <public name="system_surface_dark"/> - <public name="system_on_surface_dark"/> - <public name="system_surface_container_low_dark"/> - <public name="system_surface_container_lowest_dark"/> - <public name="system_surface_container_dark"/> - <public name="system_surface_container_high_dark"/> - <public name="system_surface_container_highest_dark"/> - <public name="system_surface_bright_dark"/> - <public name="system_surface_dim_dark"/> - <public name="system_surface_variant_dark"/> - <public name="system_on_surface_variant_dark"/> - <public name="system_outline_dark"/> - <public name="system_error_dark"/> - <public name="system_on_error_dark"/> - <public name="system_error_container_dark"/> - <public name="system_on_error_container_dark"/> - <public name="removed_system_primary_fixed_dark"/> - <public name="removed_system_primary_fixed_dim_dark"/> - <public name="removed_system_on_primary_fixed_dark"/> - <public name="removed_system_on_primary_fixed_variant_dark"/> - <public name="removed_system_secondary_fixed_dark"/> - <public name="removed_system_secondary_fixed_dim_dark"/> - <public name="removed_system_on_secondary_fixed_dark"/> - <public name="removed_system_on_secondary_fixed_variant_dark"/> - <public name="removed_system_tertiary_fixed_dark"/> - <public name="removed_system_tertiary_fixed_dim_dark"/> - <public name="removed_system_on_tertiary_fixed_dark"/> - <public name="removed_system_on_tertiary_fixed_variant_dark"/> - <public name="system_control_activated_dark"/> - <public name="system_control_normal_dark"/> - <public name="system_control_highlight_dark"/> - <public name="system_text_primary_inverse_dark"/> - <public name="system_text_secondary_and_tertiary_inverse_dark"/> - <public name="system_text_primary_inverse_disable_only_dark"/> - <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/> - <public name="system_text_hint_inverse_dark"/> - <public name="system_palette_key_color_primary_dark"/> - <public name="system_palette_key_color_secondary_dark"/> - <public name="system_palette_key_color_tertiary_dark"/> - <public name="system_palette_key_color_neutral_dark"/> - <public name="system_palette_key_color_neutral_variant_dark"/> - <public name="system_primary_fixed" /> - <public name="system_primary_fixed_dim" /> - <public name="system_on_primary_fixed" /> - <public name="system_on_primary_fixed_variant" /> - <public name="system_secondary_fixed" /> - <public name="system_secondary_fixed_dim" /> - <public name="system_on_secondary_fixed" /> - <public name="system_on_secondary_fixed_variant" /> - <public name="system_tertiary_fixed" /> - <public name="system_tertiary_fixed_dim" /> - <public name="system_on_tertiary_fixed" /> - <public name="system_on_tertiary_fixed_variant" /> - <public name="system_outline_variant_light" /> - <public name="system_outline_variant_dark" /> + <staging-public-group type="color" first-id="0x01b80000"> </staging-public-group> - <staging-public-group type="array" first-id="0x01c80000"> + <staging-public-group type="array" first-id="0x01b70000"> </staging-public-group> - <staging-public-group type="drawable" first-id="0x01c70000"> + <staging-public-group type="drawable" first-id="0x01b60000"> </staging-public-group> - <staging-public-group type="layout" first-id="0x01c60000"> + <staging-public-group type="layout" first-id="0x01b50000"> </staging-public-group> - <staging-public-group type="anim" first-id="0x01c50000"> + <staging-public-group type="anim" first-id="0x01b40000"> </staging-public-group> - <staging-public-group type="animator" first-id="0x01c40000"> + <staging-public-group type="animator" first-id="0x01b30000"> </staging-public-group> - <staging-public-group type="interpolator" first-id="0x01c30000"> + <staging-public-group type="interpolator" first-id="0x01b20000"> </staging-public-group> - <staging-public-group type="mipmap" first-id="0x01c20000"> + <staging-public-group type="mipmap" first-id="0x01b10000"> </staging-public-group> - <staging-public-group type="integer" first-id="0x01c10000"> + <staging-public-group type="integer" first-id="0x01b00000"> </staging-public-group> - <staging-public-group type="transition" first-id="0x01c00000"> + <staging-public-group type="transition" first-id="0x01af0000"> </staging-public-group> - <staging-public-group type="raw" first-id="0x01bf0000"> + <staging-public-group type="raw" first-id="0x01ae0000"> </staging-public-group> - <staging-public-group type="bool" first-id="0x01be0000"> - <!-- @hide @SystemApi --> - <public name="config_safetyProtectionEnabled" /> - <!-- @hide @SystemApi --> - <public name="config_enableDefaultNotes" /> - <!-- @hide @SystemApi --> - <public name="config_enableDefaultNotesForWorkProfile" /> + <staging-public-group type="bool" first-id="0x01ad0000"> </staging-public-group> - <staging-public-group type="fraction" first-id="0x01bd0000"> + <staging-public-group type="fraction" first-id="0x01ac0000"> </staging-public-group> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 82314072fbac..947dc2de9841 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1347,16 +1347,6 @@ <!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] --> <string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background.</string> - <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] --> - <string name="permlab_bodySensorsWristTemperature">Access body sensor wrist temperature data while the app is in use.</string> - <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] --> - <string name="permdesc_bodySensorsWristTemperature" product="default">Allows the app to access body sensor wrist temperature data, while the app is in use.</string> - - <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] --> - <string name="permlab_bodySensors_wristTemperature_background">Access body sensor wrist temperature data while the app is in the background.</string> - <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] --> - <string name="permdesc_bodySensors_wristTemperature_background" product="default">Allows the app to access body sensor wrist temperature data, while the app is in the background.</string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readCalendar">Read calendar events and details</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c6c1c8f120d7..a823d1fd8ff4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2017,6 +2017,7 @@ <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" /> <java-symbol type="integer" name="config_notificationServiceArchiveSize" /> + <java-symbol type="array" name="config_useFullScreenIntentPackages" /> <java-symbol type="integer" name="config_previousVibrationsDumpLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 316c70c45fb4..c6bb07b17fd4 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -26,7 +26,7 @@ import android.app.Activity; import android.compat.testing.PlatformCompatChangeRule; import android.os.Bundle; import android.platform.test.annotations.IwTest; -import android.platform.test.annotations.Postsubmit; +import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.util.PollingCheck; import android.view.View; @@ -60,7 +60,7 @@ import java.util.concurrent.atomic.AtomicReference; */ @RunWith(AndroidJUnit4.class) @LargeTest -@Postsubmit +@Presubmit public class FontScaleConverterActivityTest { @Rule public ActivityScenarioRule<TestActivity> rule = new ActivityScenarioRule<>(TestActivity.class); diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java new file mode 100644 index 000000000000..66f3bca72aeb --- /dev/null +++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java @@ -0,0 +1,102 @@ +/* + * 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.hardware.biometrics; + +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.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +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.MockitoJUnitRunner; +import org.mockito.junit.MockitoRule; + +import java.util.concurrent.Executor; + + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class BiometricPromptTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private Context mContext; + @Mock + private IAuthService mService; + private BiometricPrompt mBiometricPrompt; + + private CancellationSignal mCancellationSignal; + + private final TestLooper mLooper = new TestLooper(); + private final Handler mHandler = new Handler(mLooper.getLooper()); + private final Executor mExecutor = mHandler::post; + + @Before + public void setUp() throws RemoteException { + mBiometricPrompt = new BiometricPrompt.Builder(mContext) + .setUseDefaultSubtitle() + .setUseDefaultTitle() + .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG + | BiometricManager.Authenticators.DEVICE_CREDENTIAL) + .setService(mService) + .build(); + + mCancellationSignal = new CancellationSignal(); + when(mService.authenticate(any(), anyLong(), anyInt(), any(), anyString(), any())) + .thenReturn(0L); + when(mContext.getPackageName()).thenReturn("BiometricPromptTest"); + } + + @Test + public void testCancellationAfterAuthenticationFailed() throws RemoteException { + ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor = + ArgumentCaptor.forClass(IBiometricServiceReceiver.class); + BiometricPrompt.AuthenticationCallback callback = + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + super.onAuthenticationError(errorCode, errString); + }}; + mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback); + mLooper.dispatchAll(); + + verify(mService).authenticate(any(), anyLong(), anyInt(), + biometricServiceReceiverCaptor.capture(), anyString(), any()); + + biometricServiceReceiverCaptor.getValue().onAuthenticationFailed(); + mLooper.dispatchAll(); + mCancellationSignal.cancel(); + + verify(mService).cancelAuthentication(any(), anyString(), anyLong()); + } +} diff --git a/core/tests/coretests/src/android/hardware/biometrics/OWNERS b/core/tests/coretests/src/android/hardware/biometrics/OWNERS new file mode 100644 index 000000000000..6a2192a2c7fb --- /dev/null +++ b/core/tests/coretests/src/android/hardware/biometrics/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/biometrics/OWNERS diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 3b6e8eaafc22..cde100cc20aa 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -70,7 +70,13 @@ public class WindowOnBackInvokedDispatcherTest { private ApplicationInfo mApplicationInfo; private final BackMotionEvent mBackEvent = new BackMotionEvent( - 0, 0, 0, BackEvent.EDGE_LEFT, null); + /* touchX = */ 0, + /* touchY = */ 0, + /* progress = */ 0, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* swipeEdge = */ BackEvent.EDGE_LEFT, + /* departingAnimationTarget = */ null); @Before public void setUp() throws Exception { diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java index 4716312c59a8..36c2a62ae6ed 100644 --- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java @@ -23,16 +23,25 @@ import static com.android.internal.util.DumpUtils.isPlatformCriticalPackage; import static com.android.internal.util.DumpUtils.isPlatformNonCriticalPackage; import static com.android.internal.util.DumpUtils.isPlatformPackage; +import static com.google.common.truth.Truth.assertWithMessage; + import android.content.ComponentName; +import android.util.SparseArray; import junit.framework.TestCase; +import java.io.PrintWriter; +import java.io.StringWriter; + /** * Run with: atest FrameworksCoreTests:DumpUtilsTest */ public class DumpUtilsTest extends TestCase { + private final StringWriter mStringWriter = new StringWriter(); + private final PrintWriter mPrintWriter = new PrintWriter(mStringWriter); + private static ComponentName cn(String componentName) { if (componentName == null) { return null; @@ -168,4 +177,144 @@ public class DumpUtilsTest extends TestCase { Integer.toHexString(System.identityHashCode(component))).test( wcn("com.google/.abc"))); } + + public void testDumpSparseArray_empty() { + SparseArray<String> array = new SparseArray<>(); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ "...", array, "whatever"); + + String output = flushPrintWriter(); + + assertWithMessage("empty array dump").that(output).isEqualTo("...No whatevers\n"); + } + + public void testDumpSparseArray_oneElement() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number"); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".1 number(s):\n" + + "..0: 1->uno\n"); + } + + public void testDumpSparseArray_oneNullElement() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "NULL"); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".1 NULL(s):\n" + + "..0: 1->(null)\n"); + } + + public void testDumpSparseArray_multipleElements() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number"); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 number(s):\n" + + "..0: 1->uno\n" + + "..1: 2->duo\n" + + "..2: 42->(null)\n"); + } + + public void testDumpSparseArray_keyDumperOnly() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number", + (i, k) -> { + mPrintWriter.printf("_%d=%d_", i, k); + }, /* valueDumper= */ null); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 number(s):\n" + + "_0=1_uno\n" + + "_1=2_duo\n" + + "_2=42_(null)\n"); + } + + public void testDumpSparseArray_valueDumperOnly() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number", + /* keyDumper= */ null, + s -> { + mPrintWriter.print(s.toUpperCase()); + }); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 number(s):\n" + + "..0: 1->UNO\n" + + "..1: 2->DUO\n" + + "..2: 42->(null)\n"); + } + + public void testDumpSparseArray_keyAndValueDumpers() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number", + (i, k) -> { + mPrintWriter.printf("_%d=%d_", i, k); + }, + s -> { + mPrintWriter.print(s.toUpperCase()); + }); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 number(s):\n" + + "_0=1_UNO\n" + + "_1=2_DUO\n" + + "_2=42_(null)\n"); + } + + public void testDumpSparseArrayValues() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArrayValues(mPrintWriter, /* prefix= */ ".", array, "number"); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 numbers:\n" + + "..uno\n" + + "..duo\n" + + "..(null)\n"); + } + + private String flushPrintWriter() { + mPrintWriter.flush(); + + return mStringWriter.toString(); + } } diff --git a/core/tests/expresslog/OWNERS b/core/tests/expresslog/OWNERS deleted file mode 100644 index 3dc958b07f9c..000000000000 --- a/core/tests/expresslog/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 719316 -# Stats/expresslog -file:/services/core/java/com/android/server/stats/OWNERS diff --git a/core/tests/expresslog/TEST_MAPPING b/core/tests/expresslog/TEST_MAPPING deleted file mode 100644 index c9b0cf80a1e6..000000000000 --- a/core/tests/expresslog/TEST_MAPPING +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presubmit": [ - { - "name": "ExpressLogTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - } - ] - } - ] -}
\ No newline at end of file diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java deleted file mode 100644 index ee62d7528818..000000000000 --- a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java +++ /dev/null @@ -1,167 +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.internal.expresslog; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import androidx.test.filters.SmallTest; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -@SmallTest -public class ScaledRangeOptionsTest { - private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName(); - - @Test - public void testGetBinsCount() { - Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2); - assertEquals(3, options1.getBinsCount()); - - Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2); - assertEquals(12, options10.getBinsCount()); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructZeroBinsCount() { - new Histogram.ScaledRangeOptions(0, 100, 100, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructNegativeBinsCount() { - new Histogram.ScaledRangeOptions(-1, 100, 100, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructNegativeFirstBinWidth() { - new Histogram.ScaledRangeOptions(10, 100, -100, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructTooSmallFirstBinWidth() { - new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructNegativeScaleFactor() { - new Histogram.ScaledRangeOptions(10, 100, 100, -2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructTooSmallScaleFactor() { - new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructTooBigScaleFactor() { - new Histogram.ScaledRangeOptions(10, 100, 100, 500.f); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructTooBigBinRange() { - new Histogram.ScaledRangeOptions(100, 100, 100, 10.f); - } - - @Test - public void testBinIndexForRangeEqual1() { - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1); - assertEquals(12, options.getBinsCount()); - - assertEquals(11, options.getBinForSample(11)); - - for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { - assertEquals(i, options.getBinForSample(i)); - } - } - - @Test - public void testBinIndexForRangeEqual2() { - // this should produce bin otpions similar to linear histogram with bin width 2 - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1); - assertEquals(12, options.getBinsCount()); - - for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { - assertEquals(i, options.getBinForSample(i * 2)); - assertEquals(i, options.getBinForSample(i * 2 - 1)); - } - } - - @Test - public void testBinIndexForRangeEqual5() { - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1); - assertEquals(4, options.getBinsCount()); - for (int i = 0; i < 2; i++) { - for (int sample = 0; sample < 5; sample++) { - assertEquals(i + 1, options.getBinForSample(i * 5 + sample)); - } - } - } - - @Test - public void testBinIndexForRangeEqual10() { - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1); - assertEquals(0, options.getBinForSample(0)); - assertEquals(options.getBinsCount() - 2, options.getBinForSample(100)); - assertEquals(options.getBinsCount() - 1, options.getBinForSample(101)); - - final float binSize = (101 - 1) / 10f; - for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) { - assertEquals(i, options.getBinForSample(i * binSize)); - } - } - - @Test - public void testBinIndexForScaleFactor2() { - final int binsCount = 10; - final int minValue = 10; - final int firstBinWidth = 5; - final int scaledFactor = 2; - - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions( - binsCount, minValue, firstBinWidth, scaledFactor); - assertEquals(binsCount + 2, options.getBinsCount()); - long[] binCounts = new long[10]; - - // precalculate max valid value - start value for the overflow bin - int lastBinStartValue = minValue; //firstBinMin value - int lastBinWidth = firstBinWidth; - for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) { - lastBinStartValue = lastBinStartValue + lastBinWidth; - lastBinWidth *= scaledFactor; - } - - // underflow bin - for (int i = 1; i < minValue; i++) { - assertEquals(0, options.getBinForSample(i)); - } - - for (int i = 10; i < lastBinStartValue; i++) { - assertTrue(options.getBinForSample(i) > 0); - assertTrue(options.getBinForSample(i) <= binsCount); - binCounts[options.getBinForSample(i) - 1]++; - } - - // overflow bin - assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue)); - - for (int i = 1; i < binsCount; i++) { - assertEquals(binCounts[i], binCounts[i - 1] * 2L); - } - } -} diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java deleted file mode 100644 index 037dbb32c2f8..000000000000 --- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java +++ /dev/null @@ -1,125 +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.internal.expresslog; - -import androidx.test.filters.SmallTest; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -@SmallTest -public class UniformOptionsTest { - private static final String TAG = UniformOptionsTest.class.getSimpleName(); - - @Test - public void testGetBinsCount() { - Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000); - assertEquals(3, options1.getBinsCount()); - - Histogram.UniformOptions options10 = new Histogram.UniformOptions(10, 100, 1000); - assertEquals(12, options10.getBinsCount()); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructZeroBinsCount() { - new Histogram.UniformOptions(0, 100, 1000); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructNegativeBinsCount() { - new Histogram.UniformOptions(-1, 100, 1000); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructMaxValueLessThanMinValue() { - new Histogram.UniformOptions(10, 1000, 100); - } - - @Test - public void testBinIndexForRangeEqual1() { - Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11); - for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { - assertEquals(i, options.getBinForSample(i)); - } - } - - @Test - public void testBinIndexForRangeEqual2() { - Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21); - for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { - assertEquals(i, options.getBinForSample(i * 2)); - assertEquals(i, options.getBinForSample(i * 2 - 1)); - } - } - - @Test - public void testBinIndexForRangeEqual5() { - Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10); - assertEquals(4, options.getBinsCount()); - for (int i = 0; i < 2; i++) { - for (int sample = 0; sample < 5; sample++) { - assertEquals(i + 1, options.getBinForSample(i * 5 + sample)); - } - } - } - - @Test - public void testBinIndexForRangeEqual10() { - Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101); - assertEquals(0, options.getBinForSample(0)); - assertEquals(options.getBinsCount() - 2, options.getBinForSample(100)); - assertEquals(options.getBinsCount() - 1, options.getBinForSample(101)); - - final float binSize = (101 - 1) / 10f; - for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) { - assertEquals(i, options.getBinForSample(i * binSize)); - } - } - - @Test - public void testBinIndexForRangeEqual90() { - final int binCount = 10; - final int minValue = 100; - final int maxValue = 100000; - - Histogram.UniformOptions options = new Histogram.UniformOptions(binCount, minValue, - maxValue); - - // logging underflow sample - assertEquals(0, options.getBinForSample(minValue - 1)); - - // logging overflow sample - assertEquals(binCount + 1, options.getBinForSample(maxValue)); - assertEquals(binCount + 1, options.getBinForSample(maxValue + 1)); - - // logging min edge sample - assertEquals(1, options.getBinForSample(minValue)); - - // logging max edge sample - assertEquals(binCount, options.getBinForSample(maxValue - 1)); - - // logging single valid sample per bin - final int binSize = (maxValue - minValue) / binCount; - - for (int i = 0; i < binCount; i++) { - assertEquals(i + 1, options.getBinForSample(minValue + binSize * i)); - } - } -} diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml index 99594336999b..2fd65dc29363 100644 --- a/data/etc/preinstalled-packages-platform-overlays.xml +++ b/data/etc/preinstalled-packages-platform-overlays.xml @@ -56,6 +56,9 @@ <install-in-user-type package="com.android.internal.systemui.navbar.transparent"> <install-in user-type="FULL" /> </install-in-user-type> + <install-in-user-type package="com.android.role.notes.enabled"> + <install-in user-type="FULL" /> + </install-in-user-type> <install-in-user-type package="com.android.theme.color.amethyst"> <install-in user-type="FULL" /> <install-in user-type="PROFILE" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 4d858bd72f30..0eb4caaf7a0f 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -475,6 +475,18 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RecentTasks.java" }, + "-1643780158": { + "message": "Saving original orientation before camera compat, last orientation is %d", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" + }, + "-1639406696": { + "message": "NOSENSOR override detected", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java" + }, "-1638958146": { "message": "Removing activity %s from task=%s adding to task=%s Callers=%s", "level": "INFO", @@ -751,6 +763,12 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java" }, + "-1397175017": { + "message": "Other orientation overrides are in place: not reverting", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java" + }, "-1394745488": { "message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s", "level": "INFO", @@ -1711,6 +1729,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-529187878": { + "message": "Reverting orientation after camera compat force rotation", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" + }, "-521613870": { "message": "App died during pause, not stopping: %s", "level": "VERBOSE", @@ -2383,6 +2407,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "138097009": { + "message": "NOSENSOR override is absent: reverting", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java" + }, "140319294": { "message": "IME target changed within ActivityRecord", "level": "DEBUG", diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 8dd23b70ae61..2307d6080f9f 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -401,8 +401,9 @@ public final class Bitmap implements Parcelable { /** * This is called by methods that want to throw an exception if the bitmap * has already been recycled. + * @hide */ - private void checkRecycled(String errorMessage) { + void checkRecycled(String errorMessage) { if (mRecycled) { throw new IllegalStateException(errorMessage); } @@ -1921,6 +1922,7 @@ public final class Bitmap implements Parcelable { */ public void setGainmap(@Nullable Gainmap gainmap) { checkRecycled("Bitmap is recycled"); + mGainmap = null; nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr); } diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 701e20c499da..1da8e189d768 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -482,7 +482,9 @@ public class BitmapFactory { if (opts == null || opts.inBitmap == null) { return 0; } - + // Clear out the gainmap since we don't attempt to reuse it and don't want to + // accidentally keep it on the re-used bitmap + opts.inBitmap.setGainmap(null); return opts.inBitmap.getNativeInstance(); } diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index 2f6dd468511b..5c065775eea2 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -120,6 +120,7 @@ public class BitmapShader extends Shader { if (bitmap == null) { throw new IllegalArgumentException("Bitmap must be non-null"); } + bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap"); mBitmap = bitmap; mTileX = tileX; mTileY = tileY; @@ -188,6 +189,8 @@ public class BitmapShader extends Shader { /** @hide */ @Override protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) { + mBitmap.checkRecycled("BitmapShader's bitmap has been recycled"); + boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR; if (mFilterMode == FILTER_MODE_DEFAULT) { mFilterFromPaint = filterFromPaint; diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 0b29973507d2..56c3068fe5e9 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -2083,32 +2083,29 @@ public final class ImageDecoder implements AutoCloseable { } sIsP010SupportedForAV1Initialized = true; - - if (hasHardwareDecoder("video/av01")) { - sIsP010SupportedForAV1 = true; - return true; - } - - sIsP010SupportedForAV1 = Build.VERSION.DEVICE_INITIAL_SDK_INT - >= Build.VERSION_CODES.S; - return sIsP010SupportedForAV1; + return sIsP010SupportedForAV1 = isP010SupportedforMime("video/av01"); } } /** - * Checks if the device has hardware decoder for the target mime type. - */ - private static boolean hasHardwareDecoder(String mime) { - final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - for (MediaCodecInfo info : sMCL.getCodecInfos()) { - if (info.isEncoder() == false && info.isHardwareAccelerated()) { - try { - if (info.getCapabilitiesForType(mime) != null) { - return true; - } - } catch (IllegalArgumentException e) { - // mime is not supported - return false; + * Checks if the device supports decoding 10-bit for the given mime type. + */ + private static boolean isP010SupportedforMime(String mime) { + MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS); + for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) { + if (mediaCodecInfo.isEncoder()) { + continue; + } + for (String mediaType : mediaCodecInfo.getSupportedTypes()) { + if (mediaType.equalsIgnoreCase(mime)) { + MediaCodecInfo.CodecCapabilities codecCapabilities = + mediaCodecInfo.getCapabilitiesForType(mediaType); + for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) { + if (codecCapabilities.colorFormats[i] + == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) { + return true; + } + } } } } diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java index d785c3c895b8..f26b50ed4e2a 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -21,10 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; -import android.os.RemoteException; import android.os.ServiceManager; -import android.security.GenerateRkpKey; -import android.security.keymaster.KeymasterDefs; class CredstoreIdentityCredentialStore extends IdentityCredentialStore { @@ -125,18 +122,7 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore { @NonNull String docType) throws AlreadyPersonalizedException, DocTypeNotSupportedException { try { - IWritableCredential wc; - wc = mStore.createCredential(credentialName, docType); - try { - GenerateRkpKey keyGen = new GenerateRkpKey(mContext); - // We don't know what the security level is for the backing keymint, so go ahead and - // poke the provisioner for both TEE and SB. - keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT); - keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_STRONGBOX); - } catch (RemoteException e) { - // Not really an error state. Does not apply at all if RKP is unsupported or - // disabled on a given device. - } + IWritableCredential wc = mStore.createCredential(credentialName, docType); return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc); } catch (android.os.RemoteException e) { throw new RuntimeException("Unexpected RemoteException ", e); diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java deleted file mode 100644 index 698133287f63..000000000000 --- a/keystore/java/android/security/GenerateRkpKey.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security; - -import android.annotation.CheckResult; -import android.annotation.IntDef; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -/** - * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner - * app. There are two cases where Keystore should use this class. - * - * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the - * RemoteProvisioner app check if the state of the attestation key pool is getting low enough - * to warrant provisioning more attestation certificates early. - * - * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of - * attestation key pairs and cannot provide one for the given application. Keystore can then - * make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another - * attestation certificate chain provisioned. - * - * In most cases, the proper usage of (1) should preclude the need for (2). - * - * @hide - */ -public class GenerateRkpKey { - private static final String TAG = "GenerateRkpKey"; - - private static final int NOTIFY_EMPTY = 0; - private static final int NOTIFY_KEY_GENERATED = 1; - private static final int TIMEOUT_MS = 1000; - - private IGenerateRkpKeyService mBinder; - private Context mContext; - private CountDownLatch mCountDownLatch; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = { - IGenerateRkpKeyService.Status.OK, - IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY, - IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR, - IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED, - IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR, - IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR, - IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR, - IGenerateRkpKeyService.Status.INTERNAL_ERROR, - }) - public @interface Status { - } - - private ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - mBinder = IGenerateRkpKeyService.Stub.asInterface(service); - mCountDownLatch.countDown(); - } - - @Override public void onBindingDied(ComponentName className) { - mCountDownLatch.countDown(); - } - - @Override - public void onServiceDisconnected(ComponentName className) { - mBinder = null; - } - }; - - /** - * Constructor which takes a Context object. - */ - public GenerateRkpKey(Context context) { - mContext = context; - } - - @Status - private int bindAndSendCommand(int command, int securityLevel) throws RemoteException { - Intent intent = new Intent(IGenerateRkpKeyService.class.getName()); - ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); - int returnCode = IGenerateRkpKeyService.Status.OK; - if (comp == null) { - // On a system that does not use RKP, the RemoteProvisioner app won't be installed. - return returnCode; - } - intent.setComponent(comp); - mCountDownLatch = new CountDownLatch(1); - Executor executor = Executors.newCachedThreadPool(); - if (!mContext.bindService(intent, Context.BIND_AUTO_CREATE, executor, mConnection)) { - throw new RemoteException("Failed to bind to GenerateRkpKeyService"); - } - try { - mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted: ", e); - } - if (mBinder != null) { - switch (command) { - case NOTIFY_EMPTY: - returnCode = mBinder.generateKey(securityLevel); - break; - case NOTIFY_KEY_GENERATED: - mBinder.notifyKeyGenerated(securityLevel); - break; - default: - Log.e(TAG, "Invalid case for command"); - } - } else { - Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService."); - returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR; - } - mContext.unbindService(mConnection); - return returnCode; - } - - /** - * Fulfills the use case of (2) described in the class documentation. Blocks until the - * RemoteProvisioner application can get new attestation keys signed by the server. - * @return the status of the key generation - */ - @CheckResult - @Status - public int notifyEmpty(int securityLevel) throws RemoteException { - return bindAndSendCommand(NOTIFY_EMPTY, securityLevel); - } - - /** - * Fulfills the use case of (1) described in the class documentation. Non blocking call. - */ - public void notifyKeyGenerated(int securityLevel) throws RemoteException { - bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel); - } -} diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl deleted file mode 100644 index eeaeb27a7c77..000000000000 --- a/keystore/java/android/security/IGenerateRkpKeyService.aidl +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security; - -/** - * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This - * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an - * attestation request. The framework can then synchronously call generateKey() to get more - * attestation keys generated and signed. Upon return, the caller can be certain an attestation key - * is available. - * - * @hide - */ -interface IGenerateRkpKeyService { - @JavaDerive(toString=true) - @Backing(type="int") - enum Status { - /** No error(s) occurred */ - OK = 0, - /** Unable to provision keys due to a lack of internet connectivity. */ - NO_NETWORK_CONNECTIVITY = 1, - /** An error occurred while communicating with the RKP server. */ - NETWORK_COMMUNICATION_ERROR = 2, - /** The given device was not registered with the RKP backend. */ - DEVICE_NOT_REGISTERED = 4, - /** The RKP server returned an HTTP client error, indicating a misbehaving client. */ - HTTP_CLIENT_ERROR = 5, - /** The RKP server returned an HTTP server error, indicating something went wrong on the server. */ - HTTP_SERVER_ERROR = 6, - /** The RKP server returned an HTTP status that is unknown. This should never happen. */ - HTTP_UNKNOWN_ERROR = 7, - /** An unexpected internal error occurred. This should never happen. */ - INTERNAL_ERROR = 8, - } - - /** - * Ping the provisioner service to let it know an app generated a key. This may or may not have - * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check. - */ - oneway void notifyKeyGenerated(in int securityLevel); - - /** - * Ping the provisioner service to indicate there are no remaining attestation keys left. - */ - Status generateKey(in int securityLevel); -} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index c3b0f9bc16d3..474b7ea56be9 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -20,7 +20,6 @@ import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MG import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityThread; import android.content.Context; import android.hardware.security.keymint.EcCurve; import android.hardware.security.keymint.KeyParameter; @@ -28,9 +27,6 @@ import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.SecurityLevel; import android.hardware.security.keymint.Tag; import android.os.Build; -import android.os.RemoteException; -import android.security.GenerateRkpKey; -import android.security.IGenerateRkpKeyService; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore2; import android.security.KeyStoreException; @@ -621,45 +617,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato @Override public KeyPair generateKeyPair() { - GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null); - for (int i = 0; i < 2; i++) { - /** - * NOTE: There is no need to delay between re-tries because the call to - * GenerateRkpKey.notifyEmpty() will delay for a while before returning. - */ - result = generateKeyPairHelper(); - if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) { - return result.keyPair; - } - } - - // RKP failure - if (result.rkpStatus != KeyStoreException.RKP_SUCCESS) { - KeyStoreException ksException = new KeyStoreException(ResponseCode.OUT_OF_KEYS, - "Could not get RKP keys", result.rkpStatus); - throw new ProviderException("Failed to provision new attestation keys.", ksException); - } - - return result.keyPair; - } - - private static class GenerateKeyPairHelperResult { - // Zero indicates success, non-zero indicates failure. Values should be - // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE}, - // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE}, - // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY} - // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT} - public final int rkpStatus; - @Nullable - public final KeyPair keyPair; - - private GenerateKeyPairHelperResult(int rkpStatus, KeyPair keyPair) { - this.rkpStatus = rkpStatus; - this.keyPair = keyPair; - } - } - - private GenerateKeyPairHelperResult generateKeyPairHelper() { if (mKeyStore == null || mSpec == null) { throw new IllegalStateException("Not initialized"); } @@ -697,26 +654,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato AndroidKeyStorePublicKey publicKey = AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse( descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm); - GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread - .currentApplication()); - try { - if (mSpec.getAttestationChallenge() != null) { - keyGen.notifyKeyGenerated(securityLevel); - } - } catch (RemoteException e) { - // This is not really an error state, and necessarily does not apply to non RKP - // systems or hybrid systems where RKP is not currently turned on. - Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e); - } success = true; - KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey()); - return new GenerateKeyPairHelperResult(0, kp); + return new KeyPair(publicKey, publicKey.getPrivateKey()); } catch (KeyStoreException e) { switch (e.getErrorCode()) { case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: throw new StrongBoxUnavailableException("Failed to generated key pair.", e); - case ResponseCode.OUT_OF_KEYS: - return checkIfRetryableOrThrow(e, securityLevel); default: ProviderException p = new ProviderException("Failed to generate key pair.", e); if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { @@ -742,55 +685,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } - // In case keystore reports OUT_OF_KEYS, call this handler in an attempt to remotely provision - // some keys. - GenerateKeyPairHelperResult checkIfRetryableOrThrow(KeyStoreException e, int securityLevel) { - GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread - .currentApplication()); - KeyStoreException ksException; - try { - final int keyGenStatus = keyGen.notifyEmpty(securityLevel); - // Default stance: temporary error. This is a hint to the caller to try again with - // exponential back-off. - int rkpStatus; - switch (keyGenStatus) { - case IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY: - rkpStatus = KeyStoreException.RKP_FETCHING_PENDING_CONNECTIVITY; - break; - case IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED: - rkpStatus = KeyStoreException.RKP_SERVER_REFUSED_ISSUANCE; - break; - case IGenerateRkpKeyService.Status.OK: - // Explicitly return not-OK here so we retry in generateKeyPair. All other cases - // should throw because a retry doesn't make sense if we didn't actually - // provision fresh keys. - return new GenerateKeyPairHelperResult( - KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null); - case IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR: - case IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR: - case IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR: - case IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR: - case IGenerateRkpKeyService.Status.INTERNAL_ERROR: - default: - // These errors really should never happen. The best we can do is assume they - // are transient and hint to the caller to retry with back-off. - rkpStatus = KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE; - break; - } - ksException = new KeyStoreException( - ResponseCode.OUT_OF_KEYS, - "Out of RKP keys due to IGenerateRkpKeyService status: " + keyGenStatus, - rkpStatus); - } catch (RemoteException f) { - ksException = new KeyStoreException( - ResponseCode.OUT_OF_KEYS, - "Remote exception: " + f.getMessage(), - KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE); - } - ksException.initCause(e); - throw new ProviderException("Failed to provision new attestation keys.", ksException); - } - private void addAttestationParameters(@NonNull List<KeyParameter> params) throws ProviderException, IllegalArgumentException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index a5b192cd7ceb..abe8f859f2fe 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -55,20 +55,6 @@ prebuilt_etc { } // Extensions -// NOTE: This module is still under active development and must not -// be used in production. Use 'androidx.window.sidecar' instead. -android_library_import { - name: "window-extensions", - aars: ["window-extensions-release.aar"], - sdk_version: "current", -} - -android_library_import { - name: "window-extensions-core", - aars: ["window-extensions-core-release.aar"], - sdk_version: "current", -} - java_library { name: "androidx.window.extensions", srcs: [ @@ -77,8 +63,8 @@ java_library { "src/androidx/window/common/**/*.java", ], static_libs: [ - "window-extensions", - "window-extensions-core", + "androidx.window.extensions_extensions-nodeps", + "androidx.window.extensions.core_core-nodeps", ], installable: true, sdk_version: "core_platform", 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 8386131b177d..a7a6b3c92157 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -122,16 +122,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { addWindowLayoutInfoListener(activity, extConsumer); } - @Override - public void addWindowLayoutInfoListener(@NonNull @UiContext Context context, - @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) { - final Consumer<WindowLayoutInfo> extConsumer = consumer::accept; - synchronized (mLock) { - mJavaToExtConsumers.put(consumer, extConsumer); - } - addWindowLayoutInfoListener(context, extConsumer); - } - /** * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but * takes a UI Context as a parameter. diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar Binary files differdeleted file mode 100644 index 96ff840b984b..000000000000 --- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar +++ /dev/null diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differdeleted file mode 100644 index c3b6916121d0..000000000000 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ /dev/null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index e84a78f42616..133fd87a2f63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -33,12 +33,19 @@ public interface BackAnimation { * * @param touchX the X touch position of the {@link MotionEvent}. * @param touchY the Y touch position of the {@link MotionEvent}. + * @param velocityX the X velocity computed from the {@link MotionEvent}. + * @param velocityY the Y velocity computed from the {@link MotionEvent}. * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to * the process. This is forwarded separately because the input pipeline may mutate * the {#event} action state later. * @param swipeEdge the edge from which the swipe begins. */ - void onBackMotion(float touchX, float touchY, int keyAction, + void onBackMotion( + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, @BackEvent.SwipeEdge int swipeEdge); /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 210c9aab14d6..47d3a5c52074 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -256,8 +256,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private class BackAnimationImpl implements BackAnimation { @Override public void onBackMotion( - float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) { - mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge)); + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, + @BackEvent.SwipeEdge int swipeEdge + ) { + mShellExecutor.execute(() -> onMotionEvent( + /* touchX = */ touchX, + /* touchY = */ touchY, + /* velocityX = */ velocityX, + /* velocityY = */ velocityY, + /* keyAction = */ keyAction, + /* swipeEdge = */ swipeEdge)); } @Override @@ -332,13 +344,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Called when a new motion event needs to be transferred to this * {@link BackAnimationController} */ - public void onMotionEvent(float touchX, float touchY, int keyAction, + public void onMotionEvent( + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, @BackEvent.SwipeEdge int swipeEdge) { if (mPostCommitAnimationInProgress) { return; } - mTouchTracker.update(touchX, touchY); + mTouchTracker.update(touchX, touchY, velocityX, velocityY); if (keyAction == MotionEvent.ACTION_DOWN) { if (!mBackGestureStarted) { mShouldStartOnNextMoveEvent = true; @@ -561,6 +578,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } if (runner.isWaitingAnimation()) { ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready."); + // Supposed it is in post commit animation state, and start the timeout to watch + // if the animation is ready. + mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); return; } else if (runner.isAnimationCancelled()) { invokeOrCancelBack(); @@ -577,6 +597,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mPostCommitAnimationInProgress) { return; } + + mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()"); mPostCommitAnimationInProgress = true; mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); @@ -595,9 +617,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ @VisibleForTesting void onBackAnimationFinished() { - if (!mPostCommitAnimationInProgress) { - return; - } // Stop timeout runner. mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); mPostCommitAnimationInProgress = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java index 695ef4e66302..904574b08562 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -42,11 +42,13 @@ class TouchTracker { */ private float mInitTouchX; private float mInitTouchY; + private float mLatestVelocityX; + private float mLatestVelocityY; private float mStartThresholdX; private int mSwipeEdge; private boolean mCancelled; - void update(float touchX, float touchY) { + void update(float touchX, float touchY, float velocityX, float velocityY) { /** * If back was previously cancelled but the user has started swiping in the forward * direction again, restart back. @@ -58,6 +60,8 @@ class TouchTracker { } mLatestTouchX = touchX; mLatestTouchY = touchY; + mLatestVelocityX = velocityX; + mLatestVelocityY = velocityY; } void setTriggerBack(boolean triggerBack) { @@ -84,7 +88,14 @@ class TouchTracker { } BackMotionEvent createStartEvent(RemoteAnimationTarget target) { - return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target); + return new BackMotionEvent( + /* touchX = */ mInitTouchX, + /* touchY = */ mInitTouchY, + /* progress = */ 0, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* swipeEdge = */ mSwipeEdge, + /* departingAnimationTarget = */ target); } BackMotionEvent createProgressEvent() { @@ -111,7 +122,14 @@ class TouchTracker { } BackMotionEvent createProgressEvent(float progress) { - return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null); + return new BackMotionEvent( + /* touchX = */ mLatestTouchX, + /* touchY = */ mLatestTouchY, + /* progress = */ progress, + /* velocityX = */ mLatestVelocityX, + /* velocityY = */ mLatestVelocityY, + /* swipeEdge = */ mSwipeEdge, + /* departingAnimationTarget = */ null); } public void setProgressThreshold(float progressThreshold) { 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 670b24c176b5..0400963a47e8 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 @@ -25,6 +25,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context +import android.graphics.Point import android.graphics.Rect import android.os.IBinder import android.os.SystemProperties @@ -193,6 +194,21 @@ class DesktopTasksController( } } + + /** + * Move a task to fullscreen after being dragged from fullscreen and released back into + * status bar area + */ + fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) { + val wct = WindowContainerTransaction() + addMoveToFullscreenChanges(wct, task.token) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition) + } else { + shellTaskOrganizer.applyTransaction(wct) + } + } + fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) { val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task.token) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 3df2340d4524..27eda16f4171 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -17,11 +17,13 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; @@ -55,6 +57,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition public static final int FREEFORM_ANIMATION_DURATION = 336; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); + private Point mStartPosition; public EnterDesktopTaskTransitionHandler( Transitions transitions) { @@ -79,6 +82,17 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition mPendingTransitionTokens.add(token); } + /** + * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE + * @param wct WindowContainerTransaction for transition + * @param startPosition Position of task when transition is triggered + */ + public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct, + Point startPosition) { + mStartPosition = startPosition; + startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct); + } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @@ -173,6 +187,37 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition return true; } + if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && mStartPosition != null) { + // This Transition animates a task to fullscreen after being dragged from the status + // bar and then released back into the status bar area + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, null); + startT.apply(); + + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final float scale = animation.getAnimatedFraction(); + t.setPosition(sc, mStartPosition.x * (1 - scale), + mStartPosition.y * (1 - scale)); + t.setScale(sc, scale, scale); + t.apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + } + }); + animator.start(); + return true; + } + return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index f70df833cf4f..8c98c77a29ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -30,14 +30,17 @@ import android.app.TaskInfo; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.Rect; +import android.os.SystemClock; import android.view.Surface; import android.view.SurfaceControl; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.transition.Transitions; import java.lang.annotation.Retention; @@ -61,6 +64,14 @@ public class PipAnimationController { @Retention(RetentionPolicy.SOURCE) public @interface AnimationType {} + /** + * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if + * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button + * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong + * animation style to an unrelated task. + */ + private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800; + public static final int TRANSITION_DIRECTION_NONE = 0; public static final int TRANSITION_DIRECTION_SAME = 1; public static final int TRANSITION_DIRECTION_TO_PIP = 2; @@ -109,6 +120,9 @@ public class PipAnimationController { }); private PipTransitionAnimator mCurrentAnimator; + @AnimationType + private int mOneShotAnimationType = ANIM_TYPE_BOUNDS; + private long mLastOneShotAlphaAnimationTime; public PipAnimationController(PipSurfaceTransactionHelper helper) { mSurfaceTransactionHelper = helper; @@ -222,6 +236,37 @@ public class PipAnimationController { } /** + * Sets the preferred enter animation type for one time. This is typically used to set the + * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}. + * <p> + * For example, gesture navigation would first fade out the PiP activity, and the transition + * should be responsible to animate in (such as fade in) the PiP. + */ + public void setOneShotEnterAnimationType(@AnimationType int animationType) { + mOneShotAnimationType = animationType; + if (animationType == ANIM_TYPE_ALPHA) { + mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis(); + } + } + + /** Returns the preferred animation type and consumes the one-shot type if needed. */ + @AnimationType + public int takeOneShotEnterAnimationType() { + final int type = mOneShotAnimationType; + if (type == ANIM_TYPE_ALPHA) { + // Restore to default type. + mOneShotAnimationType = ANIM_TYPE_BOUNDS; + if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime + > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "Alpha animation is expired. Use bounds animation."); + return ANIM_TYPE_BOUNDS; + } + } + return type; + } + + /** * Additional callback interface for PiP animation */ public static class PipAnimationCallback { 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 a0bd064149d2..5670fe6eaeba 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 @@ -62,7 +62,6 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; -import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; import android.view.Choreographer; @@ -111,12 +110,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener { private static final String TAG = PipTaskOrganizer.class.getSimpleName(); private static final boolean DEBUG = false; - /** - * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if - * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button - * navigation, then the alpha type is unexpected. - */ - private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000; /** * The fixed start delay in ms when fading out the content overlay from bounds animation. @@ -301,8 +294,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private WindowContainerToken mToken; private SurfaceControl mLeash; protected PipTransitionState mPipTransitionState; - private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; - private long mLastOneShotAlphaAnimationTime; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; protected PictureInPictureParams mPictureInPictureParams; @@ -422,18 +413,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * Sets the preferred animation type for one time. - * This is typically used to set the animation type to - * {@link PipAnimationController#ANIM_TYPE_ALPHA}. - */ - public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) { - mOneShotAnimationType = animationType; - if (animationType == ANIM_TYPE_ALPHA) { - mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis(); - } - } - - /** * Override if the PiP should always use a fade-in animation during PiP entry. * * @return true if the mOneShotAnimationType should always be @@ -733,26 +712,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - if (mOneShotAnimationType == ANIM_TYPE_ALPHA - && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime - > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Alpha animation is expired. Use bounds animation.", TAG); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; - } - if (Transitions.ENABLE_SHELL_TRANSITIONS) { // For Shell transition, we will animate the window in PipTransition#startAnimation // instead of #onTaskAppeared. return; } - if (shouldAlwaysFadeIn()) { - mOneShotAnimationType = ANIM_TYPE_ALPHA; - } - + final int animationType = shouldAlwaysFadeIn() + ? ANIM_TYPE_ALPHA + : mPipAnimationController.takeOneShotEnterAnimationType(); if (mWaitForFixedRotation) { - onTaskAppearedWithFixedRotation(); + onTaskAppearedWithFixedRotation(animationType); return; } @@ -763,7 +733,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Objects.requireNonNull(destinationBounds, "Missing destination bounds"); final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); - if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { + if (animationType == ANIM_TYPE_BOUNDS) { if (!shouldAttachMenuEarly()) { mPipMenuController.attach(mLeash); } @@ -773,16 +743,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, null /* updateBoundsCallback */); mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); - } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + } else if (animationType == ANIM_TYPE_ALPHA) { enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { - throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType); + throw new RuntimeException("Unrecognized animation type: " + animationType); } } - private void onTaskAppearedWithFixedRotation() { - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + private void onTaskAppearedWithFixedRotation(int animationType) { + if (animationType == ANIM_TYPE_ALPHA) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG); // If deferred, hside the surface till fixed rotation is completed. @@ -791,7 +760,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, tx.setAlpha(mLeash, 0f); tx.show(mLeash); tx.apply(); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; return; } final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); @@ -1895,7 +1863,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, + " binder=" + (mToken != null ? mToken.asBinder() : null)); pw.println(innerPrefix + "mLeash=" + mLeash); pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState()); - pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType); pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 4a76a502462c..b743140b2403 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -87,7 +87,7 @@ public class PipTransition extends PipTransitionController { private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<SplitScreenController> mSplitScreenOptional; - private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; + private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; private Transitions.TransitionFinishCallback mFinishCallback; private SurfaceControl.Transaction mFinishTransaction; private final Rect mExitDestinationBounds = new Rect(); @@ -133,20 +133,6 @@ public class PipTransition extends PipTransitionController { } @Override - public void setIsFullAnimation(boolean isFullAnimation) { - setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA); - } - - /** - * Sets the preferred animation type for one time. - * This is typically used to set the animation type to - * {@link PipAnimationController#ANIM_TYPE_ALPHA}. - */ - private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) { - mOneShotAnimationType = animationType; - } - - @Override public void startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds) { if (destinationBounds != null) { @@ -288,7 +274,10 @@ public class PipTransition extends PipTransitionController { if (!requestHasPipEnter(request)) { throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); } - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn() + ? ANIM_TYPE_ALPHA + : mPipAnimationController.takeOneShotEnterAnimationType(); + if (mEnterAnimationType == ANIM_TYPE_ALPHA) { mRequestedEnterTransition = transition; mRequestedEnterTask = request.getTriggerTask().token; outWCT.setActivityWindowingMode(request.getTriggerTask().token, @@ -308,7 +297,7 @@ public class PipTransition extends PipTransitionController { @Override public boolean handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct) { - if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) { + if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) { // A fade-in was requested but not-yet started. In this case, just recalculate the // initial state under the new rotation. int rotationDelta = deltaRotation(startRotation, endRotation); @@ -760,7 +749,6 @@ public class PipTransition extends PipTransitionController { if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { - mOneShotAnimationType = ANIM_TYPE_BOUNDS; final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9]) .setPosition(leash, destinationBounds.left, destinationBounds.top) @@ -796,17 +784,16 @@ public class PipTransition extends PipTransitionController { startTransaction.setMatrix(leash, tmpTransform, new float[9]); } - if (mPipOrganizer.shouldAlwaysFadeIn()) { - mOneShotAnimationType = ANIM_TYPE_ALPHA; - } - - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + final int enterAnimationType = mEnterAnimationType; + if (enterAnimationType == ANIM_TYPE_ALPHA) { + // Restore to default type. + mEnterAnimationType = ANIM_TYPE_BOUNDS; startTransaction.setAlpha(leash, 0f); } startTransaction.apply(); PipAnimationController.PipTransitionAnimator animator; - if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { + if (enterAnimationType == ANIM_TYPE_BOUNDS) { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, rotationDelta); @@ -829,13 +816,11 @@ public class PipTransition extends PipTransitionController { animator.setColorContentOverlay(mContext); } } - } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + } else if (enterAnimationType == ANIM_TYPE_ALPHA) { animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { - throw new RuntimeException("Unrecognized animation type: " - + mOneShotAnimationType); + throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); } animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) @@ -897,7 +882,7 @@ public class PipTransition extends PipTransitionController { .setWindowCrop(leash, endBounds.width(), endBounds.height()); } } - mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction); + mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction); startTransaction.apply(); mPipOrganizer.onExitPipFinished(taskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index f51e247fe112..7979ce7a80c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -105,15 +105,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH } /** - * Called to inform the transition that the animation should start with the assumption that - * PiP is not animating from its original bounds, but rather a continuation of another - * animation. For example, gesture navigation would first fade out the PiP activity, and the - * transition should be responsible to animate in (such as fade in) the PiP. - */ - public void setIsFullAnimation(boolean isFullAnimation) { - } - - /** * Called when the Shell wants to start an exit Pip transition/animation. */ public void startExitTransition(int type, WindowContainerTransaction out, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 463ad77d828f..b0bb14b49db6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -968,12 +968,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.setShelfVisibility(visible, shelfHeight); } - private void setPinnedStackAnimationType(int animationType) { - mPipTaskOrganizer.setOneShotAnimationType(animationType); - mPipTransitionController.setIsFullAnimation( - animationType == PipAnimationController.ANIM_TYPE_BOUNDS); - } - @VisibleForTesting void setPinnedStackAnimationListener(PipAnimationListener callback) { mPinnedStackAnimationRecentsCallback = callback; @@ -1337,7 +1331,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Override public void setPipAnimationTypeToAlpha() { executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha", - (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA)); + (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType( + ANIM_TYPE_ALPHA)); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 81e118a31b73..f819bee2d5e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -129,9 +129,10 @@ interface ISplitScreen { /** * Start a pair of intents in one transition. */ - oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1, - in PendingIntent pendingIntent2, in Bundle options2, int splitPosition, - float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19; + oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1, + in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2, + in Bundle options2, int splitPosition, float splitRatio, + in RemoteTransition remoteTransition, in InstanceId instanceId) = 19; /** * Blocking call that notifies and gets additional split-screen targets when entering diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 7d5ab8428a3e..2cd16be9590c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -626,6 +626,35 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, splitPosition, splitRatio, adapter, instanceId); } + private void startIntents(PendingIntent pendingIntent1, + @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, + PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2, + @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + Intent fillInIntent1 = null; + Intent fillInIntent2 = null; + final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); + final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { + fillInIntent1 = new Intent(); + fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + fillInIntent2 = new Intent(); + fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } else { + pendingIntent2 = null; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Cancel entering split as not supporting multi-instances"); + Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, + Toast.LENGTH_SHORT).show(); + } + } + mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1, + pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio, + remoteTransition, instanceId); + } + @Override public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { @@ -1066,11 +1095,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override - public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1, - PendingIntent pendingIntent2, @Nullable Bundle options2, + public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1, + @Nullable Bundle options1, PendingIntent pendingIntent2, + @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { - // TODO(b/259368992): To be implemented. + executeRemoteCallWithTaskPermission(mController, "startIntents", + (controller) -> + controller.startIntents(pendingIntent1, shortcutInfo1, + options1, pendingIntent2, shortcutInfo2, options2, + splitPosition, splitRatio, remoteTransition, instanceId) + ); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 22800ad8e8a8..8b890bba20b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; @@ -155,8 +156,10 @@ class SplitScreenTransitions { } boolean isRootOrSplitSideRoot = change.getParent() == null || topRoot.equals(change.getParent()); - // For enter or exit, we only want to animate the side roots but not the top-root. - if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) { + boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR; + // For enter or exit, we only want to animate side roots and the divider but not the + // top-root. + if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) { continue; } @@ -165,6 +168,10 @@ class SplitScreenTransitions { t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); t.setWindowCrop(leash, change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); + } else if (isDivider) { + t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); + t.setLayer(leash, Integer.MAX_VALUE); + t.show(leash); } boolean isOpening = isOpeningTransition(info); if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { 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 dd91a37039e4..ce5a2af65646 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 @@ -682,6 +682,46 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setEnterInstanceId(instanceId); } + void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1, + @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, + PendingIntent pendingIntent2, Intent fillInIntent2, + @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, + @SplitPosition int splitPosition, float splitRatio, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (!mMainStage.isActive()) { + // Build a request WCT that will launch both apps such that task 0 is on the main stage + // while task 1 is on the side stage. + mMainStage.activate(wct, false /* reparent */); + } + + prepareEvictChildTasksIfSplitActive(wct); + mSplitLayout.setDivideRatio(splitRatio); + updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + setRootForceTranslucent(false, wct); + + setSideStagePosition(splitPosition, wct); + options1 = options1 != null ? options1 : new Bundle(); + addActivityOptions(options1, mSideStage); + if (shortcutInfo1 != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); + } else { + wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); + } + options2 = options2 != null ? options2 : new Bundle(); + addActivityOptions(options2, mMainStage); + if (shortcutInfo2 != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2); + } else { + wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2); + } + + mSplitTransitions.startEnterTransition( + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null); + setEnterInstanceId(instanceId); + } + /** Starts a pair of tasks using legacy transition. */ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, @@ -690,6 +730,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (options1 == null) options1 = new Bundle(); if (taskId2 == INVALID_TASK_ID) { // Launching a solo task. + // Exit split first if this task under split roots. + if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) { + exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); + } ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); options1 = activityOptions.toBundle(); @@ -891,10 +935,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct); } - mSyncQueue.runInSync(t -> { - setDividerVisibility(true, t); - }); - setEnterInstanceId(instanceId); } @@ -933,6 +973,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onAnimationCancelled(boolean isKeyguardOccluded) { onRemoteAnimationFinishedOrCancelled(evictWct); + setDividerVisibility(true, null); try { adapter.getRunner().onAnimationCancelled(isKeyguardOccluded); } catch (RemoteException e) { @@ -973,6 +1014,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, t.setPosition(apps[i].leash, 0, 0); } } + setDividerVisibility(true, t); t.apply(); IRemoteAnimationFinishedCallback wrapCallback = @@ -1463,6 +1505,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void finishEnterSplitScreen(SurfaceControl.Transaction t) { mSplitLayout.init(); setDividerVisibility(true, t); + // Ensure divider surface are re-parented back into the hierarchy at the end of the + // transition. See Transition#buildFinishTransaction for more detail. + t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); t.show(mRootTaskLeash); setSplitsVisible(true); @@ -1772,6 +1818,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setDividerVisibility(mainStageVisible, null); } + // Set divider visibility flag and try to apply it, the param transaction is used to apply. + // See applyDividerVisibility for more detail. private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) { if (visible == mDividerVisible) { return; @@ -1798,14 +1846,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } - if (t != null) { - applyDividerVisibility(t); - } else { - mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction)); - } + applyDividerVisibility(t); } - private void applyDividerVisibility(SurfaceControl.Transaction t) { + // Apply divider visibility by current visibility flag. If param transaction is non-null, it + // will apply by that transaction, if it is null and visible, it will run a fade-in animation, + // otherwise hide immediately. + private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) { final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); if (dividerLeash == null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, @@ -1822,7 +1869,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDividerFadeInAnimator.cancel(); } - if (mDividerVisible) { + mSplitLayout.getRefDividerBounds(mTempRect1); + if (t != null) { + t.setVisibility(dividerLeash, mDividerVisible); + t.setLayer(dividerLeash, Integer.MAX_VALUE); + t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top); + } else if (mDividerVisible) { final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f); mDividerFadeInAnimator.addUpdateListener(animation -> { @@ -1862,7 +1914,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDividerFadeInAnimator.start(); } else { - t.hide(dividerLeash); + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + transaction.hide(dividerLeash); + transaction.apply(); + mTransactionPool.release(transaction); } } @@ -2446,7 +2501,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } finishEnterSplitScreen(finishT); - addDividerBarToTransition(info, finishT, true /* show */); + addDividerBarToTransition(info, true /* show */); return true; } @@ -2589,7 +2644,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { // TODO: Have a proper remote for this. Until then, though, reset state and use the // normal animation stuff (which falls back to the normal launcher remote). - t.hide(mSplitLayout.getDividerLeash()); + setDividerVisibility(false, t); mSplitLayout.release(t); mSplitTransitions.mPendingDismiss = null; return false; @@ -2607,7 +2662,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }); } - addDividerBarToTransition(info, finishT, false /* show */); + addDividerBarToTransition(info, false /* show */); return true; } @@ -2648,11 +2703,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, logExit(EXIT_REASON_UNKNOWN); } - private void addDividerBarToTransition(@NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction finishT, boolean show) { + private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash); mSplitLayout.getRefDividerBounds(mTempRect1); + barChange.setParent(mRootTaskInfo.token); barChange.setStartAbsBounds(mTempRect1); barChange.setEndAbsBounds(mTempRect1); barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK); @@ -2660,15 +2715,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Technically this should be order-0, but this is running after layer assignment // and it's a special case, so just add to end. info.addChange(barChange); - - if (show) { - finishT.setLayer(leash, Integer.MAX_VALUE); - finishT.setPosition(leash, mTempRect1.left, mTempRect1.top); - finishT.show(leash); - // Ensure divider surface are re-parented back into the hierarchy at the end of the - // transition. See Transition#buildFinishTransaction for more detail. - finishT.reparent(leash, mRootTaskLeash); - } } RemoteAnimationTarget getDividerBarLegacyTarget() { 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 fa4de16b37f1..bdb7d44bad32 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 @@ -143,6 +143,10 @@ public class Transitions implements RemoteCallable<Transitions> { /** Transition type to fullscreen from desktop mode. */ public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12; + /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */ + public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE = + WindowManager.TRANSIT_FIRST_CUSTOM + 13; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 060dc4e05b46..dfde7e6feff5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -110,19 +110,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - final int outsetLeftId = R.dimen.freeform_resize_handle; - final int outsetTopId = R.dimen.freeform_resize_handle; - final int outsetRightId = R.dimen.freeform_resize_handle; - final int outsetBottomId = R.dimen.freeform_resize_handle; - mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mShadowRadiusId = shadowRadiusID; - if (isDragResizeable) { - mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); - } relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo 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 afc573e4fcbb..5226eeebcaa2 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 @@ -34,6 +34,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Handler; @@ -557,8 +558,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragToDesktopAnimationStarted = false; return; } else if (mDragToDesktopAnimationStarted) { - mDesktopTasksController.ifPresent(c -> - c.moveToFullscreen(relevantDecor.mTaskInfo)); + Point startPosition = new Point((int) ev.getX(), (int) ev.getY()); + mDesktopTasksController.ifPresent( + c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, + startPosition)); mDragToDesktopAnimationStarted = false; return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index f9c0e600dd38..a004e37c6345 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -208,11 +208,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - final int outsetLeftId = R.dimen.freeform_resize_handle; - final int outsetTopId = R.dimen.freeform_resize_handle; - final int outsetRightId = R.dimen.freeform_resize_handle; - final int outsetBottomId = R.dimen.freeform_resize_handle; - final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId( taskInfo.getWindowingMode()); mRelayoutParams.reset(); @@ -220,9 +215,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.mLayoutResId = windowDecorLayoutId; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mShadowRadiusId = shadowRadiusID; - if (isDragResizeable) { - mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); - } relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -424,13 +416,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_controls_window_decor) { // Align the handle menu to the left of the caption. - menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart; - menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop; + menuX = mRelayoutParams.mCaptionX + mMarginMenuStart; + menuY = mRelayoutParams.mCaptionY + mMarginMenuTop; } else { // Position the handle menu at the center of the caption. - menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2) - - mResult.mDecorContainerOffsetX; - menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart; + menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2); + menuY = mRelayoutParams.mCaptionY + mMarginMenuStart; } // App Info pill setup. @@ -497,23 +488,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final boolean pointInAppInfoPill = pointInView( mHandleMenuAppInfoPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuAppInfoPillPosition.y - - mResult.mDecorContainerOffsetY); + inputPoint.x - mHandleMenuAppInfoPillPosition.x, + inputPoint.y - mHandleMenuAppInfoPillPosition.y); boolean pointInWindowingPill = false; if (mHandleMenuWindowingPill != null) { pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuWindowingPillPosition.x - - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuWindowingPillPosition.y - - mResult.mDecorContainerOffsetY); + inputPoint.x - mHandleMenuWindowingPillPosition.x, + inputPoint.y - mHandleMenuWindowingPillPosition.y); } final boolean pointInMoreActionsPill = pointInView( mHandleMenuMoreActionsPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuMoreActionsPillPosition.x - - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuMoreActionsPillPosition.y - - mResult.mDecorContainerOffsetY); + inputPoint.x - mHandleMenuMoreActionsPillPosition.x, + inputPoint.y - mHandleMenuMoreActionsPillPosition.y); if (!pointInAppInfoPill && !pointInWindowingPill && !pointInMoreActionsPill && !pointInOpenMenuButton) { closeHandleMenu(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 8cb575cc96e3..d5437c72acac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -64,8 +64,8 @@ class DragResizeInputListener implements AutoCloseable { private final TaskResizeInputEventReceiver mInputEventReceiver; private final DragPositioningCallback mCallback; - private int mWidth; - private int mHeight; + private int mTaskWidth; + private int mTaskHeight; private int mResizeHandleThickness; private int mCornerSize; @@ -128,78 +128,84 @@ class DragResizeInputListener implements AutoCloseable { * This is also used to update the touch regions of this handler every event dispatched here is * a potential resize request. * - * @param width The width of the drag resize handler in pixels, including resize handle - * thickness. That is task width + 2 * resize handle thickness. - * @param height The height of the drag resize handler in pixels, including resize handle - * thickness. That is task height + 2 * resize handle thickness. + * @param taskWidth The width of the task. + * @param taskHeight The height of the task. * @param resizeHandleThickness The thickness of the resize handle in pixels. * @param cornerSize The size of the resize handle centered in each corner. * @param touchSlop The distance in pixels user has to drag with touch for it to register as * a resize action. */ - void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize, + void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize, int touchSlop) { - if (mWidth == width && mHeight == height + if (mTaskWidth == taskWidth && mTaskHeight == taskHeight && mResizeHandleThickness == resizeHandleThickness && mCornerSize == cornerSize) { return; } - mWidth = width; - mHeight = height; + mTaskWidth = taskWidth; + mTaskHeight = taskHeight; mResizeHandleThickness = resizeHandleThickness; mCornerSize = cornerSize; mDragDetector.setTouchSlop(touchSlop); Region touchRegion = new Region(); - final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness); + final Rect topInputBounds = new Rect( + -mResizeHandleThickness, + -mResizeHandleThickness, + mTaskWidth + mResizeHandleThickness, + 0); touchRegion.union(topInputBounds); - final Rect leftInputBounds = new Rect(0, mResizeHandleThickness, - mResizeHandleThickness, mHeight - mResizeHandleThickness); + final Rect leftInputBounds = new Rect( + -mResizeHandleThickness, + 0, + 0, + mTaskHeight); touchRegion.union(leftInputBounds); final Rect rightInputBounds = new Rect( - mWidth - mResizeHandleThickness, mResizeHandleThickness, - mWidth, mHeight - mResizeHandleThickness); + mTaskWidth, + 0, + mTaskWidth + mResizeHandleThickness, + mTaskHeight); touchRegion.union(rightInputBounds); - final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness, - mWidth, mHeight); + final Rect bottomInputBounds = new Rect( + -mResizeHandleThickness, + mTaskHeight, + mTaskWidth + mResizeHandleThickness, + mTaskHeight + mResizeHandleThickness); touchRegion.union(bottomInputBounds); // Set up touch areas in each corner. int cornerRadius = mCornerSize / 2; mLeftTopCornerBounds = new Rect( - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness + cornerRadius, - mResizeHandleThickness + cornerRadius - ); + -cornerRadius, + -cornerRadius, + cornerRadius, + cornerRadius); touchRegion.union(mLeftTopCornerBounds); mRightTopCornerBounds = new Rect( - mWidth - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness - cornerRadius, - mWidth - mResizeHandleThickness + cornerRadius, - mResizeHandleThickness + cornerRadius - ); + mTaskWidth - cornerRadius, + -cornerRadius, + mTaskWidth + cornerRadius, + cornerRadius); touchRegion.union(mRightTopCornerBounds); mLeftBottomCornerBounds = new Rect( - mResizeHandleThickness - cornerRadius, - mHeight - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness + cornerRadius, - mHeight - mResizeHandleThickness + cornerRadius - ); + -cornerRadius, + mTaskHeight - cornerRadius, + cornerRadius, + mTaskHeight + cornerRadius); touchRegion.union(mLeftBottomCornerBounds); mRightBottomCornerBounds = new Rect( - mWidth - mResizeHandleThickness - cornerRadius, - mHeight - mResizeHandleThickness - cornerRadius, - mWidth - mResizeHandleThickness + cornerRadius, - mHeight - mResizeHandleThickness + cornerRadius - ); + mTaskWidth - cornerRadius, + mTaskHeight - cornerRadius, + mTaskWidth + cornerRadius, + mTaskHeight + cornerRadius); touchRegion.union(mRightBottomCornerBounds); try { @@ -358,16 +364,16 @@ class DragResizeInputListener implements AutoCloseable { @TaskPositioner.CtrlType private int calculateResizeHandlesCtrlType(float x, float y) { int ctrlType = 0; - if (x < mResizeHandleThickness) { + if (x < 0) { ctrlType |= TaskPositioner.CTRL_TYPE_LEFT; } - if (x > mWidth - mResizeHandleThickness) { + if (x > mTaskWidth) { ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT; } - if (y < mResizeHandleThickness) { + if (y < 0) { ctrlType |= TaskPositioner.CTRL_TYPE_TOP; } - if (y > mHeight - mResizeHandleThickness) { + if (y > mTaskHeight) { ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM; } return ctrlType; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 4ebd09fdecee..bc5fd4dcbdc8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -98,7 +98,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private final Binder mOwner = new Binder(); private final Rect mCaptionInsetsRect = new Rect(); - private final Rect mTaskSurfaceCrop = new Rect(); private final float[] mTmpColor = new float[3]; WindowDecoration( @@ -218,21 +217,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); final Resources resources = mDecorWindowContext.getResources(); - outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId); - outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId); - outResult.mWidth = taskBounds.width() - + loadDimensionPixelSize(resources, params.mOutsetRightId) - - outResult.mDecorContainerOffsetX; - outResult.mHeight = taskBounds.height() - + loadDimensionPixelSize(resources, params.mOutsetBottomId) - - outResult.mDecorContainerOffsetY; - startT.setPosition( - mDecorationContainerSurface, - outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY) - .setWindowCrop(mDecorationContainerSurface, - outResult.mWidth, outResult.mHeight) + outResult.mWidth = taskBounds.width(); + outResult.mHeight = taskBounds.height(); + startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) .show(mDecorationContainerSurface); + // TODO(b/270202228): This surface can be removed. Instead, use + // |mDecorationContainerSurface| to set the background now that it no longer has outsets + // and its crop is set to the task bounds. // TaskBackgroundSurface if (mTaskBackgroundSurface == null) { final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); @@ -250,8 +242,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; - startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), - taskBounds.height()) + startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height()) .setShadowRadius(mTaskBackgroundSurface, shadowRadius) .setColor(mTaskBackgroundSurface, mTmpColor) .show(mTaskBackgroundSurface); @@ -269,11 +260,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); - startT.setPosition( - mCaptionContainerSurface, - -outResult.mDecorContainerOffsetX + params.mCaptionX, - -outResult.mDecorContainerOffsetY + params.mCaptionY) - .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) + startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) .show(mCaptionContainerSurface); if (mCaptionWindowManager == null) { @@ -314,14 +301,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Task surface itself Point taskPosition = mTaskInfo.positionInParent; - mTaskSurfaceCrop.set( - outResult.mDecorContainerOffsetX, - outResult.mDecorContainerOffsetY, - outResult.mWidth + outResult.mDecorContainerOffsetX, - outResult.mHeight + outResult.mDecorContainerOffsetY); startT.show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) - .setCrop(mTaskSurface, mTaskSurfaceCrop); + .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); } /** @@ -447,37 +429,15 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mCaptionWidthId; int mShadowRadiusId; - int mOutsetTopId; - int mOutsetBottomId; - int mOutsetLeftId; - int mOutsetRightId; - int mCaptionX; int mCaptionY; - void setOutsets(int leftId, int topId, int rightId, int bottomId) { - mOutsetLeftId = leftId; - mOutsetTopId = topId; - mOutsetRightId = rightId; - mOutsetBottomId = bottomId; - } - - void setCaptionPosition(int left, int top) { - mCaptionX = left; - mCaptionY = top; - } - void reset() { mLayoutResId = Resources.ID_NULL; mCaptionHeightId = Resources.ID_NULL; mCaptionWidthId = Resources.ID_NULL; mShadowRadiusId = Resources.ID_NULL; - mOutsetTopId = Resources.ID_NULL; - mOutsetBottomId = Resources.ID_NULL; - mOutsetLeftId = Resources.ID_NULL; - mOutsetRightId = Resources.ID_NULL; - mCaptionX = 0; mCaptionY = 0; } @@ -487,14 +447,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mWidth; int mHeight; T mRootView; - int mDecorContainerOffsetX; - int mDecorContainerOffsetY; void reset() { mWidth = 0; mHeight = 0; - mDecorContainerOffsetX = 0; - mDecorContainerOffsetY = 0; mRootView = null; } } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml index 67ca9a1a17f7..b6d92814ad43 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml @@ -29,6 +29,7 @@ <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> <option name="teardown-command" value="settings delete system show_touches" /> <option name="teardown-command" value="settings delete system pointer_location" /> + <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 806bffebd4cb..d95c7a488ea1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -166,6 +166,9 @@ public class BackAnimationControllerTest extends ShellTestCase { doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 0); mController.setTriggerBack(true); + } + + private void releaseBackGesture() { doMotionEvent(MotionEvent.ACTION_UP, 0); } @@ -201,6 +204,8 @@ public class BackAnimationControllerTest extends ShellTestCase { .setOnBackNavigationDone(new RemoteCallback(result))); triggerBackGesture(); simulateRemoteAnimationStart(type); + mShellExecutor.flushAll(); + releaseBackGesture(); simulateRemoteAnimationFinished(); mShellExecutor.flushAll(); @@ -252,6 +257,7 @@ public class BackAnimationControllerTest extends ShellTestCase { createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false); triggerBackGesture(); + releaseBackGesture(); verify(mAppCallback, times(1)).onBackInvoked(); @@ -269,6 +275,8 @@ public class BackAnimationControllerTest extends ShellTestCase { triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + releaseBackGesture(); + // Check that back invocation is dispatched. verify(mAnimatorCallback).onBackInvoked(); verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); @@ -308,6 +316,9 @@ public class BackAnimationControllerTest extends ShellTestCase { triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + mShellExecutor.flushAll(); + + releaseBackGesture(); // Simulate transition timeout. mShellExecutor.flushAll(); @@ -369,6 +380,9 @@ public class BackAnimationControllerTest extends ShellTestCase { simulateRemoteAnimationStart(type); mShellExecutor.flushAll(); + releaseBackGesture(); + mShellExecutor.flushAll(); + assertTrue("Navigation Done callback not called for " + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); assertTrue("TriggerBack should have been true", result.mTriggerBack); @@ -395,6 +409,8 @@ public class BackAnimationControllerTest extends ShellTestCase { .setOnBackNavigationDone(new RemoteCallback(result))); triggerBackGesture(); mShellExecutor.flushAll(); + releaseBackGesture(); + mShellExecutor.flushAll(); assertTrue("Navigation Done callback not called for " + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); @@ -458,9 +474,12 @@ public class BackAnimationControllerTest extends ShellTestCase { private void doMotionEvent(int actionDown, int coordinate) { mController.onMotionEvent( - coordinate, coordinate, - actionDown, - BackEvent.EDGE_LEFT); + /* touchX */ coordinate, + /* touchY */ coordinate, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* keyAction */ actionDown, + /* swipeEdge */ BackEvent.EDGE_LEFT); } private void simulateRemoteAnimationStart(int type) throws RemoteException { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 3608474bd90e..874ef80c29f0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -16,8 +16,6 @@ package com.android.wm.shell.back; -import static android.window.BackEvent.EDGE_LEFT; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -48,12 +46,21 @@ public class BackProgressAnimatorTest { private CountDownLatch mTargetProgressCalled = new CountDownLatch(1); private Handler mMainThreadHandler; + private BackMotionEvent backMotionEventFrom(float touchX, float progress) { + return new BackMotionEvent( + /* touchX = */ touchX, + /* touchY = */ 0, + /* progress = */ progress, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* swipeEdge = */ BackEvent.EDGE_LEFT, + /* departingAnimationTarget = */ null); + } + @Before public void setUp() throws Exception { mMainThreadHandler = new Handler(Looper.getMainLooper()); - final BackMotionEvent backEvent = new BackMotionEvent( - 0, 0, - 0, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(0, 0); mMainThreadHandler.post( () -> { mProgressAnimator = new BackProgressAnimator(); @@ -63,9 +70,7 @@ public class BackProgressAnimatorTest { @Test public void testBackProgressed() throws InterruptedException { - final BackMotionEvent backEvent = new BackMotionEvent( - 100, 0, - mTargetProgress, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress); mMainThreadHandler.post( () -> mProgressAnimator.onBackProgressed(backEvent)); @@ -78,9 +83,7 @@ public class BackProgressAnimatorTest { @Test public void testBackCancelled() throws InterruptedException { // Give the animator some progress. - final BackMotionEvent backEvent = new BackMotionEvent( - 100, 0, - mTargetProgress, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress); mMainThreadHandler.post( () -> mProgressAnimator.onBackProgressed(backEvent)); mTargetProgressCalled.await(1, TimeUnit.SECONDS); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java index ba9c159bad28..d62e6601723a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java @@ -47,43 +47,45 @@ public class TouchTrackerTest { public void generatesProgress_leftEdge() { mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT); float touchX = 10; + float velocityX = 0; + float velocityY = 0; // Pre-commit - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); // Post-commit touchX += 100; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); // Cancel touchX -= 10; mTouchTracker.setTriggerBack(false); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Cancel more touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restart touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restarted, but pre-commit float restartX = touchX; touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f); // Restarted, post-commit touchX += 10; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); } @@ -91,43 +93,45 @@ public class TouchTrackerTest { public void generatesProgress_rightEdge() { mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT); float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge + float velocityX = 0f; + float velocityY = 0f; // Pre-commit - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); // Post-commit touchX -= 100; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); // Cancel touchX += 10; mTouchTracker.setTriggerBack(false); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Cancel more touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restart touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restarted, but pre-commit float restartX = touchX; touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f); // Restarted, post-commit touchX -= 10; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 15bb10ed4f2b..842c699fa42d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -262,7 +262,8 @@ public class PipTaskOrganizerTest extends ShellTestCase { DisplayLayout layout = new DisplayLayout(info, mContext.getResources(), true, true); mPipDisplayLayoutState.setDisplayLayout(layout); - mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA); + doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController) + .takeOneShotEnterAnimationType(); mPipTaskOrganizer.setSurfaceControlTransactionFactory( MockSurfaceControlHelper::createMockSurfaceControlTransaction); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index eda6fdc4dbd4..e6219d1aa792 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -113,6 +113,7 @@ public class StageCoordinatorTests extends ShellTestCase { private SurfaceSession mSurfaceSession = new SurfaceSession(); private SurfaceControl mRootLeash; + private SurfaceControl mDividerLeash; private ActivityManager.RunningTaskInfo mRootTask; private StageCoordinator mStageCoordinator; private Transitions mTransitions; @@ -129,12 +130,14 @@ public class StageCoordinatorTests extends ShellTestCase { mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, Optional.empty())); + mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build(); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); when(mSplitLayout.getBounds2()).thenReturn(mBounds2); when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds); when(mSplitLayout.isLandscape()).thenReturn(false); when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true); + when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index dfa3c1010eed..e8147ff264cc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -159,14 +159,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(false) .build(); taskInfo.isFocused = false; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -213,14 +207,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .build(); taskInfo.isFocused = true; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -229,8 +217,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(decorContainerSurfaceBuilder).setParent(taskSurface); verify(decorContainerSurfaceBuilder).setContainerLayer(); verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); - verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40); - verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220); + verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100); verify(taskBackgroundSurfaceBuilder).setParent(taskSurface); verify(taskBackgroundSurfaceBuilder).setEffectLayer(); @@ -244,7 +231,6 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -268,12 +254,12 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlFinishT) .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y); verify(mMockSurfaceControlFinishT) - .setCrop(taskSurface, new Rect(-20, -40, 360, 180)); + .setWindowCrop(taskSurface, 300, 100); verify(mMockSurfaceControlStartT) .show(taskSurface); - assertEquals(380, mRelayoutResult.mWidth); - assertEquals(220, mRelayoutResult.mHeight); + assertEquals(300, mRelayoutResult.mWidth); + assertEquals(100, mRelayoutResult.mHeight); } @Test @@ -309,14 +295,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .build(); taskInfo.isFocused = true; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -419,11 +399,6 @@ public class WindowDecorationTests extends ShellTestCase { .build(); taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); windowDecor.relayout(taskInfo); @@ -438,7 +413,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(additionalWindowSurfaceBuilder).setContainerLayer(); verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface); verify(additionalWindowSurfaceBuilder).build(); - verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40); + verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0); final int width = WindowDecoration.loadDimensionPixelSize( mContext.getResources(), mCaptionMenuWidthId); final int height = WindowDecoration.loadDimensionPixelSize( @@ -496,11 +471,6 @@ public class WindowDecorationTests extends ShellTestCase { .build(); taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -508,7 +478,6 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); // Width of the captionContainerSurface should match the width of TASK_BOUNDS verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -584,9 +553,7 @@ public class WindowDecorationTests extends ShellTestCase { String name = "Test Window"; WindowDecoration.AdditionalWindow additionalWindow = addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name, - mMockSurfaceControlAddWindowT, - x - mRelayoutResult.mDecorContainerOffsetX, - y - mRelayoutResult.mDecorContainerOffsetY, + mMockSurfaceControlAddWindowT, x, y, width, height, shadowRadius, cornerRadius); return additionalWindow; } diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index d08bc5c583c2..8049dc946c9e 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -29,9 +29,10 @@ namespace android { -AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed) - : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) { - mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration()); +AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed, + SkEncodedImageFormat format) + : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed), mFormat(format) { + mTimeToShowNextSnapshot = ms2ns(currentFrameDuration()); setStagingBounds(mSkAnimatedImage->getBounds()); } @@ -92,7 +93,7 @@ bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) { // directly from mSkAnimatedImage. lock.unlock(); std::unique_lock imageLock{mImageLock}; - *outDelay = ms2ns(mSkAnimatedImage->currentFrameDuration()); + *outDelay = ms2ns(currentFrameDuration()); return true; } else { // The next snapshot has not yet been decoded, but we've already passed @@ -109,7 +110,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { Snapshot snap; { std::unique_lock lock{mImageLock}; - snap.mDurationMS = mSkAnimatedImage->decodeNextFrame(); + snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); } @@ -123,7 +124,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); - snap.mDurationMS = mSkAnimatedImage->currentFrameDuration(); + snap.mDurationMS = currentFrameDuration(); } return snap; @@ -274,7 +275,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); - durationMS = mSkAnimatedImage->currentFrameDuration(); + durationMS = currentFrameDuration(); } { std::unique_lock lock{mSwapLock}; @@ -306,7 +307,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { { std::unique_lock lock{mImageLock}; if (update) { - durationMS = mSkAnimatedImage->decodeNextFrame(); + durationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); } canvas->drawDrawable(mSkAnimatedImage.get()); @@ -336,4 +337,20 @@ SkRect AnimatedImageDrawable::onGetBounds() { return SkRectMakeLargest(); } +int AnimatedImageDrawable::adjustFrameDuration(int durationMs) { + if (durationMs == SkAnimatedImage::kFinished) { + return SkAnimatedImage::kFinished; + } + + if (mFormat == SkEncodedImageFormat::kGIF) { + // Match Chrome & Firefox behavior that gifs with a duration <= 10ms is bumped to 100ms + return durationMs <= 10 ? 100 : durationMs; + } + return durationMs; +} + +int AnimatedImageDrawable::currentFrameDuration() { + return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration()); +} + } // namespace android diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h index 8ca3c7e125f1..1e965abc82b5 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.h +++ b/libs/hwui/hwui/AnimatedImageDrawable.h @@ -16,16 +16,16 @@ #pragma once -#include <cutils/compiler.h> -#include <utils/Macros.h> -#include <utils/RefBase.h> -#include <utils/Timers.h> - #include <SkAnimatedImage.h> #include <SkCanvas.h> #include <SkColorFilter.h> #include <SkDrawable.h> +#include <SkEncodedImageFormat.h> #include <SkPicture.h> +#include <cutils/compiler.h> +#include <utils/Macros.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> #include <future> #include <mutex> @@ -48,7 +48,8 @@ class AnimatedImageDrawable : public SkDrawable { public: // bytesUsed includes the approximate sizes of the SkAnimatedImage and the SkPictures in the // Snapshots. - AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed); + AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed, + SkEncodedImageFormat format); /** * This updates the internal time and returns true if the image needs @@ -115,6 +116,7 @@ protected: private: sk_sp<SkAnimatedImage> mSkAnimatedImage; const size_t mBytesUsed; + const SkEncodedImageFormat mFormat; bool mRunning = false; bool mStarting = false; @@ -157,6 +159,9 @@ private: Properties mProperties; std::unique_ptr<OnAnimationEndListener> mEndListener; + + int adjustFrameDuration(int); + int currentFrameDuration(); }; } // namespace android diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp index 373e893b9a25..a7f5aa83e624 100644 --- a/libs/hwui/jni/AnimatedImageDrawable.cpp +++ b/libs/hwui/jni/AnimatedImageDrawable.cpp @@ -97,7 +97,7 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, bytesUsed += picture->approximateBytesUsed(); } - + SkEncodedImageFormat format = imageDecoder->mCodec->getEncodedFormat(); sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec), info, subset, std::move(picture)); @@ -108,8 +108,8 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, bytesUsed += sizeof(animatedImg.get()); - sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(std::move(animatedImg), - bytesUsed)); + sk_sp<AnimatedImageDrawable> drawable( + new AnimatedImageDrawable(std::move(animatedImg), bytesUsed, format)); return reinterpret_cast<jlong>(drawable.release()); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index cc987bcd8f0e..c4d3f5cedfa8 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -53,6 +53,8 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { } MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { + bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE); + // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. if (mHardwareBuffer) { @@ -65,6 +67,37 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { if (!mEglManager.makeCurrent(mEglSurface, &error)) { return MakeCurrentResult::AlreadyCurrent; } + + // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations + // disagree on the draw/read buffer state if the default framebuffer transitions from a surface + // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic. + // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534. + // The discussion was not resolved with a clear consensus + if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) { + GLint curReadFB = 0; + GLint curDrawFB = 0; + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB); + + GLint buffer = GL_NONE; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glGetIntegerv(GL_DRAW_BUFFER0, &buffer); + if (buffer == GL_NONE) { + const GLenum drawBuffer = GL_BACK; + glDrawBuffers(1, &drawBuffer); + } + + glGetIntegerv(GL_READ_BUFFER, &buffer); + if (buffer == GL_NONE) { + glReadBuffer(GL_BACK); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB); + + GL_CHECKPOINT(LOW); + } + return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded; } @@ -104,7 +137,8 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo); - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag, + kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); sk_sp<SkSurface> surface; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 1f929685b62c..b020e966e05a 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -28,7 +28,6 @@ #include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> -#include <SkPaintFilterCanvas.h> #include <SkPicture.h> #include <SkPictureRecorder.h> #include <SkRect.h> @@ -450,23 +449,6 @@ void SkiaPipeline::endCapture(SkSurface* surface) { } } -class ForceDitherCanvas : public SkPaintFilterCanvas { -public: - ForceDitherCanvas(SkCanvas* canvas) : SkPaintFilterCanvas(canvas) {} - -protected: - bool onFilter(SkPaint& paint) const override { - paint.setDither(true); - return true; - } - - void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { - // We unroll the drawable using "this" canvas, so that draw calls contained inside will - // get dithering applied - drawable->draw(this, matrix); - } -}; - void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect& contentDrawBounds, sk_sp<SkSurface> surface, @@ -521,12 +503,6 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, canvas->clear(SK_ColorTRANSPARENT); } - std::optional<ForceDitherCanvas> forceDitherCanvas; - if (shouldForceDither()) { - forceDitherCanvas.emplace(canvas); - canvas = &forceDitherCanvas.value(); - } - if (1 == nodes.size()) { if (!nodes[0]->nothingToDraw()) { RenderNodeDrawable root(nodes[0].get(), canvas); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 0763b06b53ef..befee8989383 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -98,8 +98,6 @@ protected: bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; } - virtual bool shouldForceDither() const { return mColorMode != ColorMode::Default; } - private: void renderFrameImpl(const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index f22652f92c15..86096d5bd01c 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -203,11 +203,6 @@ sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThr return nullptr; } -bool SkiaVulkanPipeline::shouldForceDither() const { - if (mVkSurface && mVkSurface->isBeyond8Bit()) return false; - return SkiaPipeline::shouldForceDither(); -} - void SkiaVulkanPipeline::onContextDestroyed() { if (mVkSurface) { vulkanManager().destroySurface(mVkSurface); diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index cce5468e68f0..284cde537ec0 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -63,8 +63,6 @@ public: protected: void onContextDestroyed() override; - bool shouldForceDither() const override; - private: renderthread::VulkanManager& vulkanManager(); renderthread::VulkanSurface* mVkSurface = nullptr; diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index 2b7fa0420cef..10f456745147 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -453,9 +453,15 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx]; if (bufferInfo->skSurface.get() == nullptr) { + SkSurfaceProps surfaceProps; + if (mWindowInfo.colorMode != ColorMode::Default) { + surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(), + surfaceProps.pixelGeometry()); + } bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer( mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()), - kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true); + kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps, + /*from_window=*/true); if (bufferInfo->skSurface.get() == nullptr) { ALOGE("SkSurface::MakeFromAHardwareBuffer failed"); mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, @@ -545,16 +551,6 @@ void VulkanSurface::setColorSpace(sk_sp<SkColorSpace> colorSpace) { } } -bool VulkanSurface::isBeyond8Bit() const { - switch (mWindowInfo.bufferFormat) { - case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: - case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: - return true; - default: - return false; - } -} - } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h index d3266af81437..6f5280105e55 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -49,8 +49,6 @@ public: void setColorSpace(sk_sp<SkColorSpace> colorSpace); const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; } - bool isBeyond8Bit() const; - private: /* * All structs/methods in this private section are specifically for use by the VulkanManager diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h index c7530207d1fa..060abfdc1ee5 100644 --- a/media/jni/android_media_MediaCodecLinearBlock.h +++ b/media/jni/android_media_MediaCodecLinearBlock.h @@ -44,12 +44,19 @@ struct JMediaCodecLinearBlock { std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) const { if (mBuffer) { + // TODO: if returned C2Buffer is different from mBuffer, we should + // find a way to connect the life cycle between this C2Buffer and + // mBuffer. if (mBuffer->data().type() != C2BufferData::LINEAR) { return nullptr; } C2ConstLinearBlock block = mBuffer->data().linearBlocks().front(); if (offset == 0 && size == block.capacity()) { - return mBuffer; + // Let C2Buffer be new one to queue to MediaCodec. It will allow + // the related input slot to be released by onWorkDone from C2 + // Component. Currently, the life cycle of mBuffer should be + // protected by different flows. + return std::make_shared<C2Buffer>(*mBuffer); } std::shared_ptr<C2Buffer> buffer = diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index e9b2e1041c22..3e652517270d 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -117,6 +117,8 @@ <string name="get_dialog_sign_in_type_username_separator" translatable="false">" • "</string> <!-- This text is followed by a list of one or more options. [CHAR LIMIT=80] --> <string name="get_dialog_title_sign_in_options">Sign-in options</string> + <!-- Button label for viewing the full information about an account. [CHAR LIMIT=80] --> + <string name="button_label_view_more">View more</string> <!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=80] --> <string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string> <!-- Column heading for displaying locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-ins. [CHAR LIMIT=80] --> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index ca891294576b..64addf237f9b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -252,13 +252,6 @@ class GetFlowUtils { )) } is PublicKeyCredentialEntry -> { - val passkeyUsername = credentialEntry.username.toString() - val passkeyDisplayName = credentialEntry.displayName?.toString() ?: "" - val (username, displayName) = userAndDisplayNameForPasskey( - passkeyUsername = passkeyUsername, - passkeyDisplayName = passkeyDisplayName, - ) - result.add(CredentialEntryInfo( providerId = providerId, providerDisplayName = providerLabel, @@ -268,8 +261,8 @@ class GetFlowUtils { fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.PASSKEY, credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), - userName = username, - displayName = displayName, + userName = credentialEntry.username.toString(), + displayName = credentialEntry.displayName?.toString(), icon = credentialEntry.icon.loadDrawable(context), shouldTintIcon = credentialEntry.isDefaultIcon, lastUsedTimeMillis = credentialEntry.lastUsedTime, @@ -707,7 +700,7 @@ class CreateFlowUtils { * 2) username on top if display-name is not available. * 3) don't show username on second line if username == display-name */ -private fun userAndDisplayNameForPasskey( +fun userAndDisplayNameForPasskey( passkeyUsername: String, passkeyDisplayName: String, ): Pair<String, String> { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt index 0623ff629812..2dba2ab6777c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -51,6 +51,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection @@ -79,6 +80,7 @@ fun Entry( /** If true, draws a trailing lock icon. */ isLockedAuthEntry: Boolean = false, enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, ) { val iconPadding = Modifier.wrapContentSize().padding( // Horizontal padding should be 16dp, but the suggestion chip itself @@ -103,7 +105,11 @@ fun Entry( ) { // Apply weight so that the trailing icon can always show. Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) { - SmallTitleText(text = entryHeadlineText, enforceOneLine = enforceOneLine) + SmallTitleText( + text = entryHeadlineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) if (passwordValue != null) { Row( modifier = Modifier.fillMaxWidth().padding(top = 4.dp), @@ -141,10 +147,18 @@ fun Entry( ) } } else if (entrySecondLineText != null) { - BodySmallText(text = entrySecondLineText, enforceOneLine = enforceOneLine) + BodySmallText( + text = entrySecondLineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) } if (entryThirdLineText != null) { - BodySmallText(text = entryThirdLineText, enforceOneLine = enforceOneLine) + BodySmallText( + text = entryThirdLineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) } } if (isLockedAuthEntry) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt index 14bf4f23384b..2df0c7a9b1e8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt @@ -27,8 +27,12 @@ import androidx.compose.ui.unit.dp import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme @Composable -fun CredentialListSectionHeader(text: String) { - InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurfaceVariant) +fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) { + InternalSectionHeader( + text = text, + color = LocalAndroidColorScheme.current.colorOnSurfaceVariant, + applyTopPadding = !isFirstSection + ) } @Composable @@ -37,8 +41,10 @@ fun MoreAboutPasskeySectionHeader(text: String) { } @Composable -private fun InternalSectionHeader(text: String, color: Color) { - Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 8.dp)) { +private fun InternalSectionHeader(text: String, color: Color, applyTopPadding: Boolean = false) { + Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding( + top = if (applyTopPadding) 8.dp else 0.dp + )) { SectionHeaderText( text, modifier = Modifier.padding(top = 20.dp, bottom = 8.dp), diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt index 61c03b4041f5..6b46636964e4 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt @@ -22,6 +22,7 @@ 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 com.android.credentialmanager.ui.theme.LocalAndroidColorScheme @@ -59,14 +60,20 @@ fun BodyMediumText(text: String, modifier: Modifier = Modifier) { * Body-small typography; on-surface-variant color. */ @Composable -fun BodySmallText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) { +fun BodySmallText( + text: String, + modifier: Modifier = Modifier, + enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, +) { Text( modifier = modifier.wrapContentSize(), text = text, color = LocalAndroidColorScheme.current.colorOnSurfaceVariant, style = MaterialTheme.typography.bodySmall, overflow = TextOverflow.Ellipsis, - maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE + maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE, + onTextLayout = onTextLayout, ) } @@ -87,14 +94,20 @@ fun LargeTitleText(text: String, modifier: Modifier = Modifier) { * Title-small typography; on-surface color. */ @Composable -fun SmallTitleText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) { +fun SmallTitleText( + text: String, + modifier: Modifier = Modifier, + enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, +) { Text( modifier = modifier.wrapContentSize(), text = text, color = LocalAndroidColorScheme.current.colorOnSurface, style = MaterialTheme.typography.titleSmall, overflow = TextOverflow.Ellipsis, - maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE + maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE, + onTextLayout = onTextLayout, ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 66d7db896247..96010cc66821 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -435,7 +435,7 @@ fun MoreOptionsRowIntroCard( } SheetContainerCard { item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) } - item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { Divider(thickness = 16.dp, color = Color.Transparent) } item { HeadlineText( text = stringResource( @@ -633,6 +633,7 @@ fun MoreAboutPasskeysIntroCard( } } item { + Divider(thickness = 8.dp, color = Color.Transparent) MoreAboutPasskeySectionHeader( text = stringResource(R.string.public_key_cryptography_title) ) @@ -641,6 +642,7 @@ fun MoreAboutPasskeysIntroCard( } } item { + Divider(thickness = 8.dp, color = Color.Transparent) MoreAboutPasskeySectionHeader( text = stringResource(R.string.improved_account_security_title) ) @@ -649,6 +651,7 @@ fun MoreAboutPasskeysIntroCard( } } item { + Divider(thickness = 8.dp, color = Color.Transparent) MoreAboutPasskeySectionHeader( text = stringResource(R.string.seamless_transition_title) ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 6d7ecd70eb0b..98bd020b234b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -34,11 +34,15 @@ import androidx.compose.material3.Divider import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel @@ -62,6 +66,7 @@ import com.android.credentialmanager.common.ui.HeadlineIcon import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.logging.GetCredentialEvent +import com.android.credentialmanager.userAndDisplayNameForPasskey import com.android.internal.logging.UiEventLogger.UiEventEnum @Composable @@ -158,6 +163,7 @@ fun PrimarySelectionCard( onMoreOptionSelected: () -> Unit, onLog: @Composable (UiEventEnum) -> Unit, ) { + val showMoreForTruncatedEntry = remember { mutableStateOf(false) } val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList @@ -209,6 +215,8 @@ fun PrimarySelectionCard( Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size val authenticationEntrySize = authenticationEntryList.size + // If true, render a view more button for the single truncated entry on the + // front page. // Show max 4 entries in this primary page if (usernameForCredentialSize + authenticationEntrySize <= 4) { sortedUserNameToCredentialEntryList.forEach { @@ -216,6 +224,9 @@ fun PrimarySelectionCard( credentialEntryInfo = it.sortedCredentialEntryList.first(), onEntrySelected = onEntrySelected, enforceOneLine = true, + onTextLayout = { + showMoreForTruncatedEntry.value = it.hasVisualOverflow + } ) } authenticationEntryList.forEach { @@ -269,6 +280,13 @@ fun PrimarySelectionCard( onMoreOptionSelected ) } + } else if (showMoreForTruncatedEntry.value) { + { + ActionButton( + stringResource(R.string.button_label_view_more), + onMoreOptionSelected + ) + } } else null, rightButton = if (activeEntry != null) { // Only one sign-in options exist { @@ -305,12 +323,15 @@ fun AllSignInOptionCard( bottomPadding = 0.dp, ) }) { + var isFirstSection = true // For username items(sortedUserNameToCredentialEntryList) { item -> PerUserNameCredentials( perUserNameCredentialEntryList = item, onEntrySelected = onEntrySelected, + isFirstSection = isFirstSection, ) + isFirstSection = false } // Locked password manager if (authenticationEntryList.isNotEmpty()) { @@ -318,7 +339,9 @@ fun AllSignInOptionCard( LockedCredentials( authenticationEntryList = authenticationEntryList, onEntrySelected = onEntrySelected, + isFirstSection = isFirstSection, ) + isFirstSection = false } } // From another device @@ -328,15 +351,19 @@ fun AllSignInOptionCard( RemoteEntryCard( remoteEntry = remoteEntry, onEntrySelected = onEntrySelected, + isFirstSection = isFirstSection, ) + isFirstSection = false } } // Manage sign-ins (action chips) item { ActionChips( providerInfoList = providerInfoList, - onEntrySelected = onEntrySelected + onEntrySelected = onEntrySelected, + isFirstSection = isFirstSection, ) + isFirstSection = false } } onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD) @@ -349,6 +376,7 @@ fun AllSignInOptionCard( fun ActionChips( providerInfoList: List<ProviderInfo>, onEntrySelected: (BaseEntry) -> Unit, + isFirstSection: Boolean, ) { val actionChips = providerInfoList.flatMap { it.actionEntryList } if (actionChips.isEmpty()) { @@ -356,7 +384,8 @@ fun ActionChips( } CredentialListSectionHeader( - text = stringResource(R.string.get_dialog_heading_manage_sign_ins) + text = stringResource(R.string.get_dialog_heading_manage_sign_ins), + isFirstSection = isFirstSection, ) CredentialContainerCard { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { @@ -371,9 +400,11 @@ fun ActionChips( fun RemoteEntryCard( remoteEntry: RemoteEntryInfo, onEntrySelected: (BaseEntry) -> Unit, + isFirstSection: Boolean, ) { CredentialListSectionHeader( - text = stringResource(R.string.get_dialog_heading_from_another_device) + text = stringResource(R.string.get_dialog_heading_from_another_device), + isFirstSection = isFirstSection, ) CredentialContainerCard { Column( @@ -395,9 +426,11 @@ fun RemoteEntryCard( fun LockedCredentials( authenticationEntryList: List<AuthenticationEntryInfo>, onEntrySelected: (BaseEntry) -> Unit, + isFirstSection: Boolean, ) { CredentialListSectionHeader( - text = stringResource(R.string.get_dialog_heading_locked_password_managers) + text = stringResource(R.string.get_dialog_heading_locked_password_managers), + isFirstSection = isFirstSection, ) CredentialContainerCard { Column( @@ -415,11 +448,13 @@ fun LockedCredentials( fun PerUserNameCredentials( perUserNameCredentialEntryList: PerUserNameCredentialEntryList, onEntrySelected: (BaseEntry) -> Unit, + isFirstSection: Boolean, ) { CredentialListSectionHeader( text = stringResource( R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName - ) + ), + isFirstSection = isFirstSection, ) CredentialContainerCard { Column( @@ -438,7 +473,12 @@ fun CredentialEntryRow( credentialEntryInfo: CredentialEntryInfo, onEntrySelected: (BaseEntry) -> Unit, enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, ) { + val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) + userAndDisplayNameForPasskey( + credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "") + else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName) Entry( onClick = { onEntrySelected(credentialEntryInfo) }, iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(), @@ -447,13 +487,13 @@ fun CredentialEntryRow( iconPainter = if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24) else null, - entryHeadlineText = credentialEntryInfo.userName, + entryHeadlineText = username, entrySecondLineText = if ( credentialEntryInfo.credentialType == CredentialType.PASSWORD) { "••••••••••••" } else { val itemsToDisplay = listOf( - credentialEntryInfo.displayName, + displayName, credentialEntryInfo.credentialTypeDisplayName, credentialEntryInfo.providerDisplayName ).filterNot(TextUtils::isEmpty) @@ -463,6 +503,7 @@ fun CredentialEntryRow( ) }, enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, ) } 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 47ac2df67c76..e07a6298a627 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 @@ -69,6 +69,7 @@ import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp @@ -460,6 +461,9 @@ private fun TopAppBarLayout( ProvideTextStyle(value = titleTextStyle) { CompositionLocalProvider( LocalContentColor provides titleContentColor, + // Disable the title font scaling by only passing the density but not the + // font scale. + LocalDensity provides Density(density = LocalDensity.current.density), content = title ) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt index 5342def0003d..2c3e58ece8e0 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt @@ -20,9 +20,12 @@ import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.ResolveInfo import com.android.internal.R +import com.android.settingslib.spaprivileged.framework.common.userManager import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -33,7 +36,11 @@ import kotlinx.coroutines.runBlocking */ internal interface AppListRepository { /** Loads the list of [ApplicationInfo]. */ - suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo> + suspend fun loadApps( + userId: Int, + showInstantApps: Boolean = false, + matchAnyUserForAdmin: Boolean = false, + ): List<ApplicationInfo> /** Gets the flow of predicate that could used to filter system app. */ fun showSystemPredicate( @@ -61,10 +68,12 @@ object AppListRepositoryUtil { internal class AppListRepositoryImpl(private val context: Context) : AppListRepository { private val packageManager = context.packageManager + private val userManager = context.userManager override suspend fun loadApps( userId: Int, showInstantApps: Boolean, + matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> = coroutineScope { val hiddenSystemModulesDeferred = async { packageManager.getInstalledModules(0) @@ -75,12 +84,8 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo val hideWhenDisabledPackagesDeferred = async { context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames) } - val flags = PackageManager.ApplicationInfoFlags.of( - (PackageManager.MATCH_DISABLED_COMPONENTS or - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() - ) val installedApplicationsAsUser = - packageManager.getInstalledApplicationsAsUser(flags, userId) + getInstalledApplications(userId, matchAnyUserForAdmin) val hiddenSystemModules = hiddenSystemModulesDeferred.await() val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await() @@ -89,6 +94,46 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo } } + private suspend fun getInstalledApplications( + userId: Int, + matchAnyUserForAdmin: Boolean, + ): List<ApplicationInfo> { + val regularFlags = ApplicationInfoFlags.of( + (PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() + ) + return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { + packageManager.getInstalledApplicationsAsUser(regularFlags, userId) + } else { + coroutineScope { + val deferredPackageNamesInChildProfiles = + userManager.getProfileIdsWithDisabled(userId) + .filter { it != userId } + .map { + async { + packageManager.getInstalledApplicationsAsUser(regularFlags, it) + .map { it.packageName } + } + } + val adminFlags = ApplicationInfoFlags.of( + PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value + ) + val allInstalledApplications = + packageManager.getInstalledApplicationsAsUser(adminFlags, userId) + val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles + .awaitAll() + .flatten() + .toSet() + // If an app is for a child profile and not installed on the owner, not display as + // 'not installed for this user' in the owner. This will prevent duplicates of work + // only apps showing up in the personal profile. + allInstalledApplications.filter { + it.installed || it.packageName !in packageNamesInChildProfiles + } + } + } + } + override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt index 889604209b08..bd99ebdfa3e5 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt @@ -80,12 +80,15 @@ internal open class AppListViewModelImpl<T : AppRecord>( private val scope = viewModelScope + Dispatchers.IO private val userSubGraphsFlow = appListConfig.flow.map { config -> - config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) } + config.userIds.map { userId -> + UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin) + } }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1) private inner class UserSubGraph( private val userId: Int, private val showInstantApps: Boolean, + private val matchAnyUserForAdmin: Boolean, ) { private val userIdFlow = flowOf(userId) @@ -110,7 +113,8 @@ internal open class AppListViewModelImpl<T : AppRecord>( fun reloadApps() { scope.launch { - appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps) + appsStateFlow.value = + appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin) } } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt index f4a6b59d4c4b..066db3475b36 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt @@ -62,6 +62,7 @@ private const val CONTENT_TYPE_HEADER = "header" data class AppListConfig( val userIds: List<Int>, val showInstantApps: Boolean, + val matchAnyUserForAdmin: Boolean, ) data class AppListState( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt index 2ebbe8aab809..89bfa0eb646b 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt @@ -38,6 +38,7 @@ fun <T : AppRecord> AppListPage( title: String, listModel: AppListModel<T>, showInstantApps: Boolean = false, + matchAnyUserForAdmin: Boolean = false, primaryUserOnly: Boolean = false, noItemMessage: String? = null, moreOptions: @Composable MoreOptionsScope.() -> Unit = {}, @@ -59,6 +60,7 @@ fun <T : AppRecord> AppListPage( config = AppListConfig( userIds = userGroup.userInfos.map { it.id }, showInstantApps = showInstantApps, + matchAnyUserForAdmin = matchAnyUserForAdmin, ), listModel = listModel, state = AppListState( 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 57972edc6d3a..b732a6a4495e 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 @@ -23,9 +23,13 @@ import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.PackageManager.ResolveInfoFlags import android.content.pm.ResolveInfo +import android.content.pm.UserInfo import android.content.res.Resources +import android.os.UserManager +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.R +import com.android.settingslib.spaprivileged.framework.common.userManager import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first @@ -35,10 +39,13 @@ 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.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq +import org.mockito.Mockito.verify +import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.Mockito.`when` as whenever @@ -49,8 +56,8 @@ class AppListRepositoryTest { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() - @Mock - private lateinit var context: Context + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() @Mock private lateinit var resources: Resources @@ -58,6 +65,9 @@ class AppListRepositoryTest { @Mock private lateinit var packageManager: PackageManager + @Mock + private lateinit var userManager: UserManager + private lateinit var repository: AppListRepository @Before @@ -66,36 +76,116 @@ class AppListRepositoryTest { whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(emptyArray()) whenever(context.packageManager).thenReturn(packageManager) + whenever(context.userManager).thenReturn(userManager) whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList()) whenever( - packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID)) + packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt()) ).thenReturn(emptyList()) + whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply { + flags = UserInfo.FLAG_ADMIN + }) + whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID)) + .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID)) repository = AppListRepositoryImpl(context) } - private fun mockInstalledApplications(apps: List<ApplicationInfo>) { + private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) { whenever( - packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID)) + packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId)) ).thenReturn(apps) } @Test fun loadApps_notShowInstantApps() = runTest { - mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP)) + mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + showInstantApps = false, + ) - assertThat(appListFlow).containsExactly(NORMAL_APP) + assertThat(appList).containsExactly(NORMAL_APP) } @Test fun loadApps_showInstantApps() = runTest { - mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP)) + mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + showInstantApps = true, + ) + + assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP) + } + + @Test + fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest { + mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + matchAnyUserForAdmin = false, + ) + + assertThat(appList).containsExactly(NORMAL_APP) + val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) + verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) + assertThat(flags.value.value).isEqualTo( + PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + ) + } - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true) + @Test + fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest { + mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + matchAnyUserForAdmin = true, + ) + + assertThat(appList).containsExactly(NORMAL_APP) + val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) + verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) + assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L) + } + + @Test + fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest { + val managedProfileOnlyPackageName = "installed.on.managed.profile.only" + mockInstalledApplications(listOf(ApplicationInfo().apply { + packageName = managedProfileOnlyPackageName + }), ADMIN_USER_ID) + mockInstalledApplications(listOf(ApplicationInfo().apply { + packageName = managedProfileOnlyPackageName + flags = ApplicationInfo.FLAG_INSTALLED + }), MANAGED_PROFILE_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + matchAnyUserForAdmin = true, + ) - assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP) + assertThat(appList).isEmpty() + } + + @Test + fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest { + val secondaryUserOnlyApp = ApplicationInfo().apply { + packageName = "installed.on.secondary.user.only" + } + mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID) + mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + matchAnyUserForAdmin = true, + ) + + assertThat(appList).containsExactly(secondaryUserOnlyApp) } @Test @@ -106,11 +196,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).isEmpty() + assertThat(appList).isEmpty() } @Test @@ -122,11 +212,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).isEmpty() + assertThat(appList).isEmpty() } @Test @@ -137,11 +227,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).containsExactly(app) + assertThat(appList).containsExactly(app) } @Test @@ -151,11 +241,11 @@ class AppListRepositoryTest { enabled = false enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER } - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).containsExactly(app) + assertThat(appList).containsExactly(app) } @Test @@ -164,11 +254,11 @@ class AppListRepositoryTest { packageName = "disabled" enabled = false } - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).isEmpty() + assertThat(appList).isEmpty() } @Test @@ -219,7 +309,11 @@ class AppListRepositoryTest { val app = IN_LAUNCHER_APP whenever( - packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID)) + packageManager.queryIntentActivitiesAsUser( + any(), + any<ResolveInfoFlags>(), + eq(ADMIN_USER_ID) + ) ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName))) val showSystemPredicate = getShowSystemPredicate(showSystem = false) @@ -229,12 +323,16 @@ class AppListRepositoryTest { @Test fun getSystemPackageNames_returnExpectedValues() = runTest { - mockInstalledApplications(listOf( - NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP)) + mockInstalledApplications( + apps = listOf( + NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP + ), + userId = ADMIN_USER_ID, + ) val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames( context = context, - userId = USER_ID, + userId = ADMIN_USER_ID, showInstantApps = false, ) @@ -243,12 +341,13 @@ class AppListRepositoryTest { private suspend fun getShowSystemPredicate(showSystem: Boolean) = repository.showSystemPredicate( - userIdFlow = flowOf(USER_ID), + userIdFlow = flowOf(ADMIN_USER_ID), showSystemFlow = flowOf(showSystem), ).first() private companion object { - const val USER_ID = 0 + const val ADMIN_USER_ID = 0 + const val MANAGED_PROFILE_USER_ID = 11 val NORMAL_APP = ApplicationInfo().apply { packageName = "normal" diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt index 4f0cddef078b..36d9db5ef251 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt @@ -85,7 +85,11 @@ class AppListViewModelTest { } private object FakeAppListRepository : AppListRepository { - override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP) + override suspend fun loadApps( + userId: Int, + showInstantApps: Boolean, + matchAnyUserForAdmin: Boolean, + ) = listOf(APP) override fun showSystemPredicate( userIdFlow: Flow<Int>, @@ -112,7 +116,11 @@ class AppListViewModelTest { const val USER_ID = 0 const val PACKAGE_NAME = "package.name" const val LABEL = "Label" - val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false) + val CONFIG = AppListConfig( + userIds = listOf(USER_ID), + showInstantApps = false, + matchAnyUserForAdmin = false, + ) val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt index a99d02de0df0..241a134b6a76 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt @@ -114,7 +114,11 @@ class AppListTest { ) { composeTestRule.setContent { AppListInput( - config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false), + config = AppListConfig( + userIds = listOf(USER_ID), + showInstantApps = false, + matchAnyUserForAdmin = false, + ), listModel = TestAppListModel(enableGrouping = enableGrouping), state = AppListState( showSystem = false.toState(), diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java new file mode 100644 index 000000000000..5326e73a3f82 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java @@ -0,0 +1,53 @@ +/* + * 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.settingslib.fuelgauge; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Utilities related to battery saver logging. + */ +public final class BatterySaverLogging { + /** + * Record the reason while enabling power save mode manually. + * See {@link SaverManualEnabledReason} for all available states. + */ + public static final String EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON = + "extra_power_save_mode_manual_enabled_reason"; + + /** Broadcast action to record battery saver manual enabled reason. */ + public static final String ACTION_SAVER_MANUAL_ENABLED_REASON = + "com.android.settingslib.fuelgauge.ACTION_SAVER_MANUAL_ENABLED_REASON"; + + /** An interface for the battery saver manual enable reason. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({SAVER_ENABLED_UNKNOWN, SAVER_ENABLED_CONFIRMATION, SAVER_ENABLED_VOICE, + SAVER_ENABLED_SETTINGS, SAVER_ENABLED_QS, SAVER_ENABLED_LOW_WARNING, + SAVER_ENABLED_SEVERE_WARNING}) + public @interface SaverManualEnabledReason {} + + public static final int SAVER_ENABLED_UNKNOWN = 0; + public static final int SAVER_ENABLED_CONFIRMATION = 1; + public static final int SAVER_ENABLED_VOICE = 2; + public static final int SAVER_ENABLED_SETTINGS = 3; + public static final int SAVER_ENABLED_QS = 4; + public static final int SAVER_ENABLED_LOW_WARNING = 5; + public static final int SAVER_ENABLED_SEVERE_WARNING = 6; +} diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index 52f3111d967c..e28ada4771c9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java @@ -16,6 +16,10 @@ package com.android.settingslib.fuelgauge; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_MANUAL_ENABLED_REASON; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason; + import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -116,9 +120,9 @@ public class BatterySaverUtils { * @return true if the request succeeded. */ public static synchronized boolean setPowerSaveMode(Context context, - boolean enable, boolean needFirstTimeWarning) { + boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) { if (DEBUG) { - Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF")); + Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason); } final ContentResolver cr = context.getContentResolver(); @@ -145,8 +149,10 @@ public class BatterySaverUtils { && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0 && Secure.getInt(cr, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) { - showAutoBatterySaverSuggestion(context, confirmationExtras); + sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION, + confirmationExtras); } + recordBatterySaverEnabledReason(context, reason); } return true; @@ -175,21 +181,23 @@ public class BatterySaverUtils { // Already shown. return false; } - context.sendBroadcast( - getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION, extras)); + sendSystemUiBroadcast(context, ACTION_SHOW_START_SAVER_CONFIRMATION, extras); return true; } - private static void showAutoBatterySaverSuggestion(Context context, Bundle extras) { - context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION, extras)); + private static void recordBatterySaverEnabledReason(Context context, + @SaverManualEnabledReason int reason) { + final Bundle enabledReasonExtras = new Bundle(1); + enabledReasonExtras.putInt(EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON, reason); + sendSystemUiBroadcast(context, ACTION_SAVER_MANUAL_ENABLED_REASON, enabledReasonExtras); } - private static Intent getSystemUiBroadcast(String action, Bundle extras) { - final Intent i = new Intent(action); - i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); - i.setPackage(SYSUI_PACKAGE); - i.putExtras(extras); - return i; + private static void sendSystemUiBroadcast(Context context, String action, Bundle extras) { + final Intent intent = new Intent(action); + intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.setPackage(SYSUI_PACKAGE); + intent.putExtras(extras); + context.sendBroadcast(intent); } private static void setBatterySaverConfirmationAcknowledged(Context context) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java index ad022a63eaf6..cb386fbff4ed 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java @@ -16,6 +16,7 @@ package com.android.settingslib.fuelgauge; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN; import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE; import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE; @@ -72,7 +73,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null"); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isFalse(); verify(mMockContext, times(1)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean()); @@ -92,7 +94,8 @@ public class BatterySaverUtilsTest { Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -111,7 +114,8 @@ public class BatterySaverUtilsTest { Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1); Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -129,7 +133,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null"); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -147,7 +152,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); // When disabling, needFirstTimeWarning doesn't matter. - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false)); @@ -166,7 +172,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); // When disabling, needFirstTimeWarning doesn't matter. - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false)); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index fedfb43535cc..78d93bd19a15 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -58,7 +58,6 @@ <uses-permission android:name="android.permission.ACCEPT_HANDOVER" /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.BODY_SENSORS" /> - <uses-permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" /> <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" /> <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" /> <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" /> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 3007d4a79d13..7a1d9a3ad025 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -156,9 +156,10 @@ android_library { "WifiTrackerLib", "WindowManager-Shell", "SystemUIAnimationLib", + "SystemUICommon", + "SystemUICustomizationLib", "SystemUIPluginLib", "SystemUISharedLib", - "SystemUICustomizationLib", "SystemUI-statsd", "SettingsLib", "androidx.core_core-ktx", diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 36a0b5dd72b8..a00f401756f7 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -347,15 +347,6 @@ <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" /> - <!-- Intent Chooser --> - <permission - android:name="android.permission.ADD_CHOOSER_PINS" - android:protectionLevel="signature" /> - <uses-permission android:name="android.permission.ADD_CHOOSER_PINS" /> - <permission - android:name="android.permission.RECEIVE_CHOOSER_PIN_MIGRATION" - android:protectionLevel="signature" /> - <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> @@ -993,7 +984,13 @@ android:exported="true" android:excludeFromRecents="true" android:resizeableActivity="false" - android:theme="@android:style/Theme.NoDisplay" /> + android:theme="@android:style/Theme.NoDisplay" > + + <intent-filter> + <action android:name="com.android.systemui.action.LAUNCH_NOTE_TASK"/> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> <!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller to specify an Android user when launching the default notes app. --> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml new file mode 100644 index 000000000000..1d67066028be --- /dev/null +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/action_bar_title" + style="@style/TextAppearance.AppCompat.Title" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:maxLines="5"/> +</LinearLayout> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java index 02d279fa4962..5ed450abede5 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility.accessibilitymenu.activity; +import android.app.ActionBar; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -24,6 +25,7 @@ import android.net.Uri; import android.os.Bundle; import android.provider.Browser; import android.provider.Settings; +import android.widget.TextView; import android.view.View; import androidx.annotation.Nullable; @@ -46,6 +48,13 @@ public class A11yMenuSettingsActivity extends FragmentActivity { .beginTransaction() .replace(android.R.id.content, new A11yMenuPreferenceFragment()) .commit(); + + ActionBar actionBar = getActionBar(); + actionBar.setDisplayShowCustomEnabled(true); + actionBar.setCustomView(R.layout.preferences_action_bar); + ((TextView) findViewById(R.id.action_bar_title)).setText( + getResources().getString(R.string.accessibility_menu_settings_name) + ); } /** diff --git a/packages/SystemUI/common/.gitignore b/packages/SystemUI/common/.gitignore new file mode 100644 index 000000000000..f9a33dbbcc7e --- /dev/null +++ b/packages/SystemUI/common/.gitignore @@ -0,0 +1,9 @@ +.idea/ +.gradle/ +gradle/ +build/ +gradlew* +local.properties +*.iml +android.properties +buildSrc
\ No newline at end of file diff --git a/core/tests/expresslog/Android.bp b/packages/SystemUI/common/Android.bp index cab49a76a734..e36ada89b207 100644 --- a/core/tests/expresslog/Android.bp +++ b/packages/SystemUI/common/Android.bp @@ -15,33 +15,25 @@ package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], } -android_test { - name: "ExpressLogTests", +android_library { + + name: "SystemUICommon", srcs: [ "src/**/*.java", + "src/**/*.kt", ], static_libs: [ - "androidx.test.rules", - "modules-utils-build", - ], - - libs: [ - "android.test.base", - "android.test.runner", - ], - - platform_apis: true, - test_suites: [ - "general-tests", + "androidx.core_core-ktx", ], - certificate: "platform", + manifest: "AndroidManifest.xml", + kotlincflags: ["-Xjvm-default=all"], } diff --git a/core/tests/expresslog/AndroidManifest.xml b/packages/SystemUI/common/AndroidManifest.xml index 94a39e06c974..6f757eb67d2e 100644 --- a/core/tests/expresslog/AndroidManifest.xml +++ b/packages/SystemUI/common/AndroidManifest.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project +<!-- + 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. @@ -15,15 +16,6 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - android:installLocation="internalOnly" - package="com.android.internal.expresslog" > - - <application > - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.internal.expresslog" - android:label="Telemetry Express Logging Helper Tests" /> + package="com.android.systemui.common"> </manifest> diff --git a/packages/SystemUI/common/OWNERS b/packages/SystemUI/common/OWNERS new file mode 100644 index 000000000000..9b8a79e6f3c7 --- /dev/null +++ b/packages/SystemUI/common/OWNERS @@ -0,0 +1,2 @@ +darrellshi@google.com +evanlaird@google.com diff --git a/packages/SystemUI/common/README.md b/packages/SystemUI/common/README.md new file mode 100644 index 000000000000..1cc5277aa83e --- /dev/null +++ b/packages/SystemUI/common/README.md @@ -0,0 +1,5 @@ +# SystemUICommon + +`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended to be used by other modules, and therefore should not have other SystemUI dependencies to avoid circular dependencies. + +To maintain the structure of this module, please refrain from adding components at the top level. Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to keep the module organized and easy to navigate. diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt index 4773f54a079e..de49d1c2c5ee 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt +++ b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.plugins.util +package com.android.systemui.common.buffer import kotlin.math.max diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp index fb1c454de70d..e306d4aac398 100644 --- a/packages/SystemUI/plugin/Android.bp +++ b/packages/SystemUI/plugin/Android.bp @@ -37,6 +37,7 @@ java_library { "error_prone_annotations", "PluginCoreLib", "SystemUIAnimationLib", + "SystemUICommon", ], } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 322fc774e805..05630e795476 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -199,6 +199,9 @@ data class ClockConfig( */ val hasCustomPositionUpdatedAnimation: Boolean = false, + /** Transition to AOD should move smartspace like large clock instead of small clock */ + val useAlternateSmartspaceAODTransition: Boolean = false, + /** True if the clock will react to tone changes in the seed color. */ val isReactiveToTone: Boolean = true, ) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt index 52dfc55c105d..f71c137363c5 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.annotation.VisibleForTesting class WeatherData -private constructor( +constructor( val description: String, val state: WeatherStateIcon, val useCelsius: Boolean, diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt index 3e34885a6d9c..4a6e0b61ecc9 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt @@ -18,7 +18,7 @@ package com.android.systemui.plugins.log import android.os.Trace import android.util.Log -import com.android.systemui.plugins.util.RingBuffer +import com.android.systemui.common.buffer.RingBuffer import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.concurrent.ArrayBlockingQueue diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml new file mode 100644 index 000000000000..61a1cb5f1d31 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<animated-vector + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25"> + <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/> + <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/> + <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/> + <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/> + </group> + <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"> + <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/> + </group> + </group> + <group android:name="time_group"/> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_3_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/> + </set> + </aapt:attr> + </target> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml index 951d6fed0a17..f3325ecefada 100644 --- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml +++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml @@ -104,4 +104,9 @@ android:fromId="@id/unlocked" android:toId="@id/locked" android:drawable="@drawable/unlocked_to_locked" /> + + <transition + android:fromId="@id/locked_fp" + android:toId="@id/locked" + android:drawable="@drawable/fp_to_locked" /> </animated-selector> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 2143fc4db852..edd3047203b3 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -21,19 +21,19 @@ <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] --> <string name="keyguard_enter_your_pin">Enter your PIN</string> - <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] --> + <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] --> <string name="keyguard_enter_pin">Enter PIN</string> <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] --> <string name="keyguard_enter_your_pattern">Enter your pattern</string> - <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] --> + <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] --> <string name="keyguard_enter_pattern">Draw pattern</string> <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] --> <string name="keyguard_enter_your_password">Enter your password</string> - <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] --> + <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] --> <string name="keyguard_enter_password">Enter password</string> <!-- Shown in the lock screen when there is SIM card IO error. --> @@ -128,103 +128,103 @@ <!-- Message shown when user enters wrong pattern --> <string name="kg_wrong_pattern">Wrong pattern</string> - <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] --> + <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] --> <string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string> <!-- Message shown when user enters wrong password --> <string name="kg_wrong_password">Wrong password</string> - <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] --> + <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] --> <string name="kg_wrong_password_try_again">Wrong password. Try again.</string> <!-- Message shown when user enters wrong PIN --> <string name="kg_wrong_pin">Wrong PIN</string> - <!-- Message shown when user enters wrong PIN [CHAR LIMIT=26] --> + <!-- Message shown when user enters wrong PIN [CHAR LIMIT=48] --> <string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string> - <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=52] --> + <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=70] --> <string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string> - <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=26] --> + <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=48] --> <string name="kg_fp_not_recognized">Fingerprint not recognized</string> - <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=26] --> + <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=48] --> <string name="bouncer_face_not_recognized">Face not recognized</string> - <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] --> + <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] --> <string name="kg_bio_try_again_or_pin">Try again or enter PIN</string> - <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] --> + <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] --> <string name="kg_bio_try_again_or_password">Try again or enter password</string> - <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] --> + <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] --> <string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string> - <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] --> + <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] --> <string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string> - <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] --> + <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] --> <string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string> - <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] --> + <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] --> <string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string> - <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] --> + <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] --> <string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string> - <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] --> + <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] --> <string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string> - <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] --> + <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] --> <string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string> - <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=52] --> + <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=70] --> <string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string> - <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] --> + <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] --> <string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string> - <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] --> + <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] --> <string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string> - <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] --> + <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] --> <string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string> - <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=52] --> + <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=70] --> <string name="kg_prompt_unattended_update">Update will install during inactive hours</string> - <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] --> + <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] --> <string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string> - <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] --> + <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] --> <string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string> - <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] --> + <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=76] --> <string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string> - <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=52] --> + <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=82] --> <string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string> - <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=52] --> + <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=70] --> <string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string> - <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=52] --> + <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=75] --> <string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string> - <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=52] --> + <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=70] --> <string name="kg_trust_agent_disabled">Trust agent is unavailable</string> - <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] --> + <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] --> <string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string> - <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] --> + <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] --> <string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string> - <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] --> + <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] --> <string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string> - <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]--> + <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]--> <string name="kg_too_many_failed_attempts_countdown">{count, plural, =1 {Try again in # second.} other {Try again in # seconds.} @@ -296,13 +296,13 @@ <!-- Description of airplane mode --> <string name="airplane_mode">Airplane mode</string> - <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] --> + <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] --> <string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string> - <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] --> + <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] --> <string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string> - <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] --> + <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] --> <string name="kg_prompt_reason_restart_password">Password is required after device restarts</string> <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] --> diff --git a/packages/SystemUI/res/drawable/chipbar_background.xml b/packages/SystemUI/res/drawable/chipbar_background.xml index 57221776a32f..7530f5ba244a 100644 --- a/packages/SystemUI/res/drawable/chipbar_background.xml +++ b/packages/SystemUI/res/drawable/chipbar_background.xml @@ -17,6 +17,6 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <solid android:color="?androidprv:attr/colorAccentSecondary" /> + <solid android:color="?androidprv:attr/materialColorSecondaryFixed" /> <corners android:radius="32dp" /> </shape> diff --git a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml index 80c7207a35b6..a3832eef957f 100644 --- a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml +++ b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml @@ -20,7 +20,7 @@ android:color="?android:textColorPrimary"> <item android:id="@android:id/background"> <shape> - <solid android:color="@android:color/system_accent1_200"/> + <solid android:color="?androidprv:attr/materialColorPrimaryFixedDim"/> <corners android:radius="24dp" /> </shape> </item> diff --git a/packages/SystemUI/res/drawable/hearing.xml b/packages/SystemUI/res/drawable/hearing.xml new file mode 100644 index 000000000000..02f5f92ec5fe --- /dev/null +++ b/packages/SystemUI/res/drawable/hearing.xml @@ -0,0 +1,24 @@ +<!-- 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/> +</vector> diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml index 762dcdced9c4..8fa975be43d2 100644 --- a/packages/SystemUI/res/layout/chipbar.xml +++ b/packages/SystemUI/res/layout/chipbar.xml @@ -49,15 +49,17 @@ android:alpha="0.0" /> + <!-- LINT.IfChange textColor --> <TextView android:id="@+id/text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:textSize="@dimen/chipbar_text_size" - android:textColor="@color/chipbar_text_and_icon_color" + style="@style/Chipbar.Text" + android:textColor="?androidprv:attr/materialColorOnSecondaryFixed" android:alpha="0.0" /> + <!-- LINT.ThenChange(systemui.temporarydisplay.chipbar.ChipbarInfo.kt) --> <!-- At most one of [loading, failure_icon, undo] will be visible at a time. --> <ImageView @@ -66,7 +68,7 @@ android:layout_height="@dimen/chipbar_end_icon_size" android:layout_marginStart="@dimen/chipbar_end_item_start_margin" android:src="@drawable/ic_progress_activity" - android:tint="@android:color/system_accent2_700" + android:tint="?androidprv:attr/materialColorOnSecondaryFixedVariant" android:alpha="0.0" /> @@ -84,9 +86,9 @@ android:id="@+id/end_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="?androidprv:attr/textColorOnAccent" android:layout_marginStart="@dimen/chipbar_end_item_start_margin" - android:textSize="@dimen/chipbar_text_size" + style="@style/Chipbar.Text" + android:textColor="?androidprv:attr/materialColorOnPrimaryFixed" android:paddingStart="@dimen/chipbar_outer_padding" android:paddingEnd="@dimen/chipbar_outer_padding" android:paddingTop="@dimen/chipbar_end_button_vertical_padding" diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index c0f7029449a1..e9acf3fa10c8 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -64,20 +64,20 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:layout_gravity="bottom" + android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset" + android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset" + android:gravity="bottom" > <com.android.systemui.animation.view.LaunchableImageView android:id="@+id/start_button" android:layout_height="@dimen/keyguard_affordance_fixed_height" android:layout_width="@dimen/keyguard_affordance_fixed_width" - android:layout_gravity="bottom|start" android:scaleType="fitCenter" android:padding="@dimen/keyguard_affordance_fixed_padding" android:tint="?android:attr/textColorPrimary" android:background="@drawable/keyguard_bottom_affordance_bg" android:foreground="@drawable/keyguard_bottom_affordance_selected_border" - android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset" - android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset" android:visibility="invisible" /> <FrameLayout @@ -90,7 +90,7 @@ android:id="@+id/keyguard_settings_button" layout="@layout/keyguard_settings_popup_menu" android:layout_width="wrap_content" - android:layout_height="@dimen/keyguard_affordance_fixed_height" + android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="gone" /> @@ -100,14 +100,11 @@ android:id="@+id/end_button" android:layout_height="@dimen/keyguard_affordance_fixed_height" android:layout_width="@dimen/keyguard_affordance_fixed_width" - android:layout_gravity="bottom|end" android:scaleType="fitCenter" android:padding="@dimen/keyguard_affordance_fixed_padding" android:tint="?android:attr/textColorPrimary" android:background="@drawable/keyguard_bottom_affordance_bg" android:foreground="@drawable/keyguard_bottom_affordance_selected_border" - android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset" - android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset" android:visibility="invisible" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml index 9d0d783d0113..65ee8b370f9b 100644 --- a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml +++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml @@ -20,7 +20,8 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" - android:layout_height="48dp" + android:layout_height="wrap_content" + android:minHeight="@dimen/keyguard_affordance_fixed_height" android:orientation="horizontal" android:gravity="center_vertical" android:background="@drawable/keyguard_settings_popup_menu_background" diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index f1fca7603571..d7106762d17b 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -152,10 +152,4 @@ <include layout="@layout/keyguard_bottom_area" android:visibility="gone" /> - - <FrameLayout - android:id="@+id/preview_container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - </FrameLayout> </com.android.systemui.shade.NotificationPanelView> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 96e6d4e1a234..cb8c2a73c15d 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -231,9 +231,6 @@ <color name="people_tile_background">@color/material_dynamic_secondary95</color> - <!-- Chipbar --> - <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color> - <!-- Internet Dialog --> <!-- Material next state on color--> <color name="settingslib_state_on_color">@color/settingslib_state_on</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4db42aacbdcd..0eb0a078a478 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1110,6 +1110,7 @@ <!-- Chipbar --> <!-- (Used for media tap-to-transfer chip for sender device and active unlock) --> <dimen name="chipbar_outer_padding">16dp</dimen> + <dimen name="chipbar_outer_padding_half">8dp</dimen> <dimen name="chipbar_text_size">16sp</dimen> <dimen name="chipbar_start_icon_size">24dp</dimen> <dimen name="chipbar_end_icon_size">20dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 2098aea87ab2..a359d3b21938 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -70,6 +70,15 @@ <item name="android:fontWeight">700</item> </style> + <style name="Chipbar" /> + + <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title"> + <!-- Text size should be kept in sync with the notification conversation header size. (The + conversation header doesn't have a defined style, so the size must be copied here.) + See notification_template_conversation_header.xml. --> + <item name="android:textSize">16sp</item> + </style> + <style name="TextAppearance" /> <style name="TextAppearance.QS"> diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java index 7971e84769a2..b15378570358 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -21,7 +21,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.net.wifi.WifiManager; +import android.os.Trace; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; @@ -37,6 +37,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository; import com.android.systemui.telephony.TelephonyListenerManager; import java.util.List; @@ -50,7 +51,10 @@ import javax.inject.Inject; * Controller that generates text including the carrier names and/or the status of all the SIM * interfaces in the device. Through a callback, the updates can be retrieved either as a list or * separated by a given separator {@link CharSequence}. + * + * @deprecated use {@link com.android.systemui.statusbar.pipeline.wifi} instead */ +@Deprecated public class CarrierTextManager { private static final boolean DEBUG = KeyguardConstants.DEBUG; private static final String TAG = "CarrierTextController"; @@ -64,7 +68,7 @@ public class CarrierTextManager { private final AtomicBoolean mNetworkSupported = new AtomicBoolean(); @VisibleForTesting protected KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final WifiManager mWifiManager; + private final WifiRepository mWifiRepository; private final boolean[] mSimErrorState; private final int mSimSlotsNumber; @Nullable // Check for nullability before dispatching @@ -165,7 +169,7 @@ public class CarrierTextManager { CharSequence separator, boolean showAirplaneMode, boolean showMissingSim, - @Nullable WifiManager wifiManager, + WifiRepository wifiRepository, TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WakefulnessLifecycle wakefulnessLifecycle, @@ -177,8 +181,7 @@ public class CarrierTextManager { mShowAirplaneMode = showAirplaneMode; mShowMissingSim = showMissingSim; - - mWifiManager = wifiManager; + mWifiRepository = wifiRepository; mTelephonyManager = telephonyManager; mSeparator = separator; mTelephonyListenerManager = telephonyListenerManager; @@ -297,6 +300,7 @@ public class CarrierTextManager { } protected void updateCarrierText() { + Trace.beginSection("CarrierTextManager#updateCarrierText"); boolean allSimsMissing = true; boolean anySimReadyAndInService = false; CharSequence displayText = null; @@ -329,20 +333,20 @@ public class CarrierTextManager { carrierNames[i] = carrierTextForSimState; } if (simState == TelephonyManager.SIM_STATE_READY) { + Trace.beginSection("WFC check"); ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) { // hack for WFC (IWLAN) not turning off immediately once // Wi-Fi is disassociated or disabled if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN - || (mWifiManager != null && mWifiManager.isWifiEnabled() - && mWifiManager.getConnectionInfo() != null - && mWifiManager.getConnectionInfo().getBSSID() != null)) { + || mWifiRepository.isWifiConnectedWithValidSsid()) { if (DEBUG) { Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); } anySimReadyAndInService = true; } } + Trace.endSection(); } } // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY @@ -406,6 +410,7 @@ public class CarrierTextManager { subsIds, airplaneMode); postToCallback(info); + Trace.endSection(); } @VisibleForTesting @@ -633,7 +638,7 @@ public class CarrierTextManager { public static class Builder { private final Context mContext; private final String mSeparator; - private final WifiManager mWifiManager; + private final WifiRepository mWifiRepository; private final TelephonyManager mTelephonyManager; private final TelephonyListenerManager mTelephonyListenerManager; private final WakefulnessLifecycle mWakefulnessLifecycle; @@ -647,7 +652,7 @@ public class CarrierTextManager { public Builder( Context context, @Main Resources resources, - @Nullable WifiManager wifiManager, + @Nullable WifiRepository wifiRepository, TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WakefulnessLifecycle wakefulnessLifecycle, @@ -657,7 +662,7 @@ public class CarrierTextManager { mContext = context; mSeparator = resources.getString( com.android.internal.R.string.kg_text_message_separator); - mWifiManager = wifiManager; + mWifiRepository = wifiRepository; mTelephonyManager = telephonyManager; mTelephonyListenerManager = telephonyListenerManager; mWakefulnessLifecycle = wakefulnessLifecycle; @@ -681,7 +686,7 @@ public class CarrierTextManager { /** Create a CarrierTextManager. */ public CarrierTextManager build() { return new CarrierTextManager( - mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager, + mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository, mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor); } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 0779653430b2..7262a736c433 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -25,6 +25,7 @@ import android.graphics.Rect import android.text.format.DateFormat import android.util.TypedValue import android.view.View +import android.view.View.OnAttachStateChangeListener import android.view.ViewTreeObserver import android.widget.FrameLayout import androidx.annotation.VisibleForTesting @@ -105,6 +106,24 @@ constructor( } updateFontSizes() updateTimeListeners() + value.smallClock.view.addOnAttachStateChangeListener( + object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(p0: View?) { + value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + + override fun onViewDetachedFromWindow(p0: View?) { + } + }) + value.largeClock.view.addOnAttachStateChangeListener( + object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(p0: View?) { + value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + + override fun onViewDetachedFromWindow(p0: View?) { + } + }) } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt index 3a89c13ddd64..40f6f48288dc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt @@ -17,9 +17,9 @@ package com.android.keyguard import android.annotation.CurrentTimeMillisLong +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer /** Verbose debug information. */ data class KeyguardActiveUnlockModel( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 9290220b8698..25d17928351a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -109,7 +109,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final ContentObserver mShowWeatherObserver = new ContentObserver(null) { @Override public void onChange(boolean change) { - setDateWeatherVisibility(); + setWeatherVisibility(); } }; @@ -236,6 +236,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS updateDoubleLineClock(); setDateWeatherVisibility(); + setWeatherVisibility(); mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( mKeyguardUnlockAnimationListener); @@ -266,6 +267,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mStatusArea.removeView(mDateWeatherView); addDateWeatherView(index); } + setDateWeatherVisibility(); + setWeatherVisibility(); } int index = mStatusArea.indexOfChild(mSmartspaceView); if (index >= 0) { @@ -487,16 +490,19 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void setDateWeatherVisibility() { - if (mDateWeatherView != null || mWeatherView != null) { + if (mDateWeatherView != null) { mUiExecutor.execute(() -> { - if (mDateWeatherView != null) { - mDateWeatherView.setVisibility( - clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE); - } - if (mWeatherView != null) { - mWeatherView.setVisibility( - mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE); - } + mDateWeatherView.setVisibility( + clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE); + }); + } + } + + private void setWeatherVisibility() { + if (mWeatherView != null) { + mUiExecutor.execute(() -> { + mWeatherView.setVisibility( + mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE); }); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt index c98e9b40e7ab..5b0e29005d82 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -17,9 +17,9 @@ package com.android.keyguard import android.annotation.CurrentTimeMillisLong +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer /** Verbose debug information associated. */ data class KeyguardFaceListenModel( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt index 57130ed80d26..b8c0ccbd8aaa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt @@ -17,9 +17,9 @@ package com.android.keyguard import android.annotation.CurrentTimeMillisLong +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer /** Verbose debug information. */ data class KeyguardFingerprintListenModel( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index b8e196fb8787..d8e1eb0f0860 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -19,10 +19,12 @@ package com.android.keyguard; import static java.util.Collections.emptySet; import android.content.Context; +import android.os.Build; import android.os.Trace; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import android.widget.GridLayout; import com.android.systemui.R; @@ -118,6 +120,16 @@ public class KeyguardStatusView extends GridLayout { } @Override + public ViewPropertyAnimator animate() { + if (Build.IS_DEBUGGABLE) { + throw new IllegalArgumentException( + "KeyguardStatusView does not support ViewPropertyAnimator. " + + "Use PropertyAnimator instead."); + } + return super.animate(); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Trace.beginSection("KeyguardStatusView#onMeasure"); super.onMeasure(widthMeasureSpec, heightMeasureSpec); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index c4df836e401f..0cdef4d63639 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -16,12 +16,37 @@ package com.android.keyguard; +import static androidx.constraintlayout.widget.ConstraintSet.END; +import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; + +import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; + +import android.animation.Animator; +import android.animation.ValueAnimator; import android.annotation.Nullable; import android.graphics.Rect; +import android.transition.ChangeBounds; +import android.transition.Transition; +import android.transition.TransitionListenerAdapter; +import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.transition.TransitionValues; import android.util.Slog; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import androidx.annotation.VisibleForTesting; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; + +import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.KeyguardClockSwitch.ClockSize; import com.android.keyguard.logging.KeyguardLogger; +import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ClockController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -42,7 +67,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private static final boolean DEBUG = KeyguardConstants.DEBUG; private static final String TAG = "KeyguardStatusViewController"; - private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = + /** + * Duration to use for the animator when the keyguard status view alignment changes, and a + * custom clock animation is in use. + */ + private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000; + + public static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); private final KeyguardSliceViewController mKeyguardSliceViewController; @@ -50,8 +81,25 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; + private final FeatureFlags mFeatureFlags; + private final InteractionJankMonitor mInteractionJankMonitor; private final Rect mClipBounds = new Rect(); + private Boolean mStatusViewCentered = true; + + private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = + new TransitionListenerAdapter() { + @Override + public void onTransitionCancel(Transition transition) { + mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + } + + @Override + public void onTransitionEnd(Transition transition) { + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + } + }; + @Inject public KeyguardStatusViewController( KeyguardStatusView keyguardStatusView, @@ -62,7 +110,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConfigurationController configurationController, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, - KeyguardLogger logger) { + KeyguardLogger logger, + FeatureFlags featureFlags, + InteractionJankMonitor interactionJankMonitor) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; mKeyguardClockSwitchController = keyguardClockSwitchController; @@ -71,6 +121,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, logger.getBuffer()); + mInteractionJankMonitor = interactionJankMonitor; + mFeatureFlags = featureFlags; } @Override @@ -169,16 +221,32 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mView.setImportantForAccessibility(mode); } + @VisibleForTesting + void setProperty(AnimatableProperty property, float value, boolean animate) { + PropertyAnimator.setProperty(mView, property, value, CLOCK_ANIMATION_PROPERTIES, animate); + } + /** * Update position of the view with an optional animation */ public void updatePosition(int x, int y, float scale, boolean animate) { float oldY = mView.getY(); - PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES, - animate); + setProperty(AnimatableProperty.Y, y, animate); + + ClockController clock = mKeyguardClockSwitchController.getClock(); + if (clock != null && clock.getConfig().getUseAlternateSmartspaceAODTransition()) { + // If requested, scale the entire view instead of just the clock view + mKeyguardClockSwitchController.updatePosition(x, 1f /* scale */, + CLOCK_ANIMATION_PROPERTIES, animate); + setProperty(AnimatableProperty.SCALE_X, scale, animate); + setProperty(AnimatableProperty.SCALE_Y, scale, animate); + } else { + mKeyguardClockSwitchController.updatePosition(x, scale, + CLOCK_ANIMATION_PROPERTIES, animate); + setProperty(AnimatableProperty.SCALE_X, 1f, animate); + setProperty(AnimatableProperty.SCALE_Y, 1f, animate); + } - mKeyguardClockSwitchController.updatePosition(x, scale, CLOCK_ANIMATION_PROPERTIES, - animate); if (oldY != y) { mKeyguardClockSwitchController.updateKeyguardStatusViewOffset(); } @@ -242,9 +310,141 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } } - /** Gets the current clock controller. */ - @Nullable - public ClockController getClockController() { - return mKeyguardClockSwitchController.getClock(); + /** + * Updates the alignment of the KeyguardStatusView and animates the transition if requested. + */ + public void updateAlignment( + ConstraintLayout notifContainerParent, + boolean splitShadeEnabled, + boolean shouldBeCentered, + boolean animate) { + if (mStatusViewCentered == shouldBeCentered) { + return; + } + + mStatusViewCentered = shouldBeCentered; + if (notifContainerParent == null) { + return; + } + + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(notifContainerParent); + int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline; + constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END); + if (!animate) { + constraintSet.applyTo(notifContainerParent); + return; + } + + mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + ChangeBounds transition = new ChangeBounds(); + if (splitShadeEnabled) { + // Excluding media from the transition on split-shade, as it doesn't transition + // horizontally properly. + transition.excludeTarget(R.id.status_view_media_container, true); + } + + transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + + ClockController clock = mKeyguardClockSwitchController.getClock(); + boolean customClockAnimation = clock != null + && clock.getConfig().getHasCustomPositionUpdatedAnimation(); + + if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { + // Find the clock, so we can exclude it from this transition. + FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large); + + // The clock container can sometimes be null. If it is, just fall back to the + // old animation rather than setting up the custom animations. + if (clockContainerView == null || clockContainerView.getChildCount() == 0) { + transition.addListener(mKeyguardStatusAlignmentTransitionListener); + TransitionManager.beginDelayedTransition(notifContainerParent, transition); + } else { + View clockView = clockContainerView.getChildAt(0); + + transition.excludeTarget(clockView, /* exclude= */ true); + + TransitionSet set = new TransitionSet(); + set.addTransition(transition); + + SplitShadeTransitionAdapter adapter = + new SplitShadeTransitionAdapter(mKeyguardClockSwitchController); + + // Use linear here, so the actual clock can pick its own interpolator. + adapter.setInterpolator(Interpolators.LINEAR); + adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION); + adapter.addTarget(clockView); + set.addTransition(adapter); + set.addListener(mKeyguardStatusAlignmentTransitionListener); + TransitionManager.beginDelayedTransition(notifContainerParent, set); + } + } else { + transition.addListener(mKeyguardStatusAlignmentTransitionListener); + TransitionManager.beginDelayedTransition(notifContainerParent, transition); + } + + constraintSet.applyTo(notifContainerParent); + } + + @VisibleForTesting + static class SplitShadeTransitionAdapter extends Transition { + private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds"; + private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS }; + + private final KeyguardClockSwitchController mController; + + @VisibleForTesting + SplitShadeTransitionAdapter(KeyguardClockSwitchController controller) { + mController = controller; + } + + private void captureValues(TransitionValues transitionValues) { + Rect boundsRect = new Rect(); + boundsRect.left = transitionValues.view.getLeft(); + boundsRect.top = transitionValues.view.getTop(); + boundsRect.right = transitionValues.view.getRight(); + boundsRect.bottom = transitionValues.view.getBottom(); + transitionValues.values.put(PROP_BOUNDS, boundsRect); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Nullable + @Override + public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues, + @Nullable TransitionValues endValues) { + if (startValues == null || endValues == null) { + return null; + } + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + + Rect from = (Rect) startValues.values.get(PROP_BOUNDS); + Rect to = (Rect) endValues.values.get(PROP_BOUNDS); + + anim.addUpdateListener(animation -> { + ClockController clock = mController.getClock(); + if (clock == null) { + return; + } + + clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction()); + }); + + return anim; + } + + @Override + public String[] getTransitionProperties() { + return TRANSITION_PROPERTIES; + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index e1bca89091b2..350c4ed084b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -96,6 +96,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.SensorProperties; +import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; @@ -1278,6 +1279,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) { lockedOutStateChanged = !mFaceLockedOutPermanent; mFaceLockedOutPermanent = true; + if (isFaceClass3()) { + updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); + } } if (isHwUnavailable && cameraPrivacyEnabled) { @@ -1487,8 +1491,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent; // however the strong auth tracker does not include the temporary lockout // mFingerprintLockedOut. + // Class 3 biometric lockout will lockout ALL biometrics return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric) - && !mFingerprintLockedOut; + && (!isFingerprintClass3() || !isFingerprintLockedOut()) + && (!isFaceClass3() || !mFaceLockedOutPermanent); } /** @@ -1506,9 +1512,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @NonNull BiometricSourceType biometricSourceType) { switch (biometricSourceType) { case FINGERPRINT: - return isUnlockingWithBiometricAllowed(true); + return isUnlockingWithBiometricAllowed(isFingerprintClass3()); case FACE: - return isUnlockingWithBiometricAllowed(false); + return isUnlockingWithBiometricAllowed(isFaceClass3()); default: return false; } @@ -2473,7 +2479,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private void updateFaceEnrolled(int userId) { - Boolean isFaceEnrolled = mFaceManager != null && !mFaceSensorProperties.isEmpty() + final Boolean isFaceEnrolled = isFaceSupported() && mBiometricEnabledForUser.get(userId) && mAuthController.isFaceAuthEnrolled(userId); if (mIsFaceEnrolled != isFaceEnrolled) { @@ -2482,10 +2488,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mIsFaceEnrolled = isFaceEnrolled; } - public boolean isFaceSupported() { + private boolean isFaceSupported() { return mFaceManager != null && !mFaceSensorProperties.isEmpty(); } + private boolean isFingerprintSupported() { + return mFpm != null && !mFingerprintSensorProperties.isEmpty(); + } + /** * @return true if there's at least one udfps enrolled for the current user. */ @@ -2792,10 +2802,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || !mLockPatternUtils.isSecure(user); // Don't trigger active unlock if fp is locked out - final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent; + final boolean fpLockedOut = isFingerprintLockedOut(); // Don't trigger active unlock if primary auth is required - final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true); + final boolean primaryAuthRequired = !isUnlockingWithTrustAgentAllowed(); final boolean shouldTriggerActiveUnlock = (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard) @@ -2857,7 +2867,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || mGoingToSleep || shouldListenForFingerprintAssistant || (mKeyguardOccluded && mIsDreaming) - || (mKeyguardOccluded && userDoesNotHaveTrust + || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing && (mOccludingAppRequestingFp || isUdfps || mAlternateBouncerShowing)); // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an @@ -2949,7 +2959,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // allow face detection to happen even if stronger auth is required. When face is detected, // we show the bouncer. However, if the user manually locked down the device themselves, // never attempt to detect face. - final boolean supportsDetect = !mFaceSensorProperties.isEmpty() + final boolean supportsDetect = isFaceSupported() && mFaceSensorProperties.get(0).supportsFaceDetection && canBypass && !mPrimaryBouncerIsOrWillBeShowing && !isUserInLockdown(user); @@ -3104,7 +3114,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab : WAKE_REASON_UNKNOWN ).toFaceAuthenticateOptions(); // This would need to be updated for multi-sensor devices - final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() + final boolean supportsFaceDetection = isFaceSupported() && mFaceSensorProperties.get(0).supportsFaceDetection; if (!isUnlockingWithBiometricAllowed(FACE)) { final boolean udfpsFingerprintAuthRunning = isUdfpsSupported() @@ -3166,21 +3176,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @return {@code true} if possible. */ public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) { - // This assumes that there is at most one face and at most one fingerprint sensor - return (mFaceManager != null && !mFaceSensorProperties.isEmpty() - && (mFaceSensorProperties.get(0).sensorStrength != SensorProperties.STRENGTH_STRONG) - && isUnlockWithFacePossible(userId)) - || (mFpm != null && !mFingerprintSensorProperties.isEmpty() - && (mFingerprintSensorProperties.get(0).sensorStrength - != SensorProperties.STRENGTH_STRONG) && isUnlockWithFingerprintPossible(userId)); + return (!isFaceClass3() && isUnlockWithFacePossible(userId)) + || (isFingerprintClass3() && isUnlockWithFingerprintPossible(userId)); } @SuppressLint("MissingPermission") @VisibleForTesting boolean isUnlockWithFingerprintPossible(int userId) { // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once. - boolean newFpEnrolled = mFpm != null - && !mFingerprintSensorProperties.isEmpty() + boolean newFpEnrolled = isFingerprintSupported() && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId); Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false); if (oldFpEnrolled != newFpEnrolled) { @@ -3330,12 +3334,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Immediately stop previous biometric listening states. // Resetting lockout states updates the biometric listening states. - if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) { + if (isFaceSupported()) { stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING); handleFaceLockoutReset(mFaceManager.getLockoutModeForUser( mFaceSensorProperties.get(0).sensorId, userId)); } - if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) { + if (isFingerprintSupported()) { stopListeningForFingerprint(); handleFingerprintLockoutReset(mFpm.getLockoutModeForUser( mFingerprintSensorProperties.get(0).sensorId, userId)); @@ -4071,6 +4075,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return BIOMETRIC_LOCKOUT_RESET_DELAY_MS; } + @VisibleForTesting + protected boolean isFingerprintClass3() { + // This assumes that there is at most one fingerprint sensor property + return isFingerprintSupported() && isClass3Biometric(mFingerprintSensorProperties.get(0)); + } + + @VisibleForTesting + protected boolean isFaceClass3() { + // This assumes that there is at most one face sensor property + return isFaceSupported() && isClass3Biometric(mFaceSensorProperties.get(0)); + } + + private boolean isClass3Biometric(SensorPropertiesInternal sensorProperties) { + return sensorProperties.sensorStrength == SensorProperties.STRENGTH_STRONG; + } + /** * Unregister all listeners. */ @@ -4122,11 +4142,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int subId : mServiceStates.keySet()) { pw.println(" " + subId + "=" + mServiceStates.get(subId)); } - if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) { + if (isFingerprintSupported()) { final int userId = mUserTracker.getUserId(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId); pw.println(" Fingerprint state (user=" + userId + ")"); + pw.println(" isFingerprintClass3=" + isFingerprintClass3()); pw.println(" areAllFpAuthenticatorsRegistered=" + mAuthController.areAllFingerprintAuthenticatorsRegistered()); pw.println(" allowed=" @@ -4184,11 +4205,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.toList() ).printTableData(pw); } - if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) { + if (isFaceSupported()) { final int userId = mUserTracker.getUserId(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); BiometricAuthenticated face = mUserFaceAuthenticated.get(userId); pw.println(" Face authentication state (user=" + userId + ")"); + pw.println(" isFaceClass3=" + isFaceClass3()); pw.println(" allowed=" + (face != null && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric))); pw.println(" auth'd=" diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index a678edc0eb06..651c9796140e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -18,8 +18,8 @@ package com.android.keyguard; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import android.util.Property; import android.view.View; -import android.view.ViewPropertyAnimator; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.log.LogBuffer; @@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.google.errorprone.annotations.CompileTimeConstant; +import java.util.function.Consumer; + /** * Helper class for updating visibility of keyguard views based on keyguard and status bar state. * This logic is shared by both the keyguard status view and the keyguard user switcher. @@ -83,47 +85,49 @@ public class KeyguardVisibilityHelper { boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState) { - mView.animate().cancel(); + PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA); boolean isOccluded = mKeyguardStateController.isOccluded(); mKeyguardViewVisibilityAnimating = false; if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD && statusBarState != KEYGUARD) || goingToFullShade) { mKeyguardViewVisibilityAnimating = true; - mView.animate() - .alpha(0f) - .setStartDelay(0) - .setDuration(160) - .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction( - mAnimateKeyguardStatusViewGoneEndRunnable); + + AnimationProperties animProps = new AnimationProperties() + .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_OUT) + .setAnimationEndAction(mSetGoneEndAction); if (keyguardFadingAway) { - mView.animate() - .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay()) - .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration()) - .start(); + animProps + .setDelay(mKeyguardStateController.getKeyguardFadingAwayDelay()) + .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration()); log("goingToFullShade && keyguardFadingAway"); } else { + animProps.setDelay(0).setDuration(160); log("goingToFullShade && !keyguardFadingAway"); } + PropertyAnimator.setProperty( + mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */); } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) { mView.setVisibility(View.VISIBLE); mKeyguardViewVisibilityAnimating = true; mView.setAlpha(0f); - mView.animate() - .alpha(1f) - .setStartDelay(0) - .setDuration(320) - .setInterpolator(Interpolators.ALPHA_IN) - .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); + PropertyAnimator.setProperty( + mView, AnimatableProperty.ALPHA, 1f, + new AnimationProperties() + .setDelay(0) + .setDuration(320) + .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_IN) + .setAnimationEndAction( + property -> mSetVisibleEndRunnable.run()), + true /* animate */); log("keyguardFadingAway transition w/ Y Aniamtion"); } else if (statusBarState == KEYGUARD) { if (keyguardFadingAway) { mKeyguardViewVisibilityAnimating = true; - ViewPropertyAnimator animator = mView.animate() - .alpha(0) - .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) - .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable); + AnimationProperties animProps = new AnimationProperties() + .setDelay(0) + .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_LINEAR_IN) + .setAnimationEndAction(mSetInvisibleEndAction); if (mAnimateYPos) { float target = mView.getY() - mView.getHeight() * 0.05f; int delay = 0; @@ -135,13 +139,16 @@ public class KeyguardVisibilityHelper { PropertyAnimator.setProperty(mView, AnimatableProperty.Y, target, mAnimationProperties, true /* animate */); - animator.setDuration(duration) - .setStartDelay(delay); + animProps.setDuration(duration) + .setDelay(delay); log("keyguardFadingAway transition w/ Y Aniamtion"); } else { log("keyguardFadingAway transition w/o Y Animation"); } - animator.start(); + PropertyAnimator.setProperty( + mView, AnimatableProperty.ALPHA, 0f, + animProps, + true /* animate */); } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { log("ScreenOff transition"); mKeyguardViewVisibilityAnimating = true; @@ -149,7 +156,7 @@ public class KeyguardVisibilityHelper { // Ask the screen off animation controller to animate the keyguard visibility for us // since it may need to be cancelled due to keyguard lifecycle events. mScreenOffAnimationController.animateInKeyguard( - mView, mAnimateKeyguardStatusViewVisibleEndRunnable); + mView, mSetVisibleEndRunnable); } else { log("Direct set Visibility to VISIBLE"); mView.setVisibility(View.VISIBLE); @@ -163,19 +170,25 @@ public class KeyguardVisibilityHelper { mLastOccludedState = isOccluded; } - private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> { - mKeyguardViewVisibilityAnimating = false; - mView.setVisibility(View.INVISIBLE); - log("Callback Set Visibility to INVISIBLE"); + private final Consumer<Property> mSetInvisibleEndAction = new Consumer<>() { + @Override + public void accept(Property property) { + mKeyguardViewVisibilityAnimating = false; + mView.setVisibility(View.INVISIBLE); + log("Callback Set Visibility to INVISIBLE"); + } }; - private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> { - mKeyguardViewVisibilityAnimating = false; - mView.setVisibility(View.GONE); - log("CallbackSet Visibility to GONE"); + private final Consumer<Property> mSetGoneEndAction = new Consumer<>() { + @Override + public void accept(Property property) { + mKeyguardViewVisibilityAnimating = false; + mView.setVisibility(View.GONE); + log("CallbackSet Visibility to GONE"); + } }; - private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> { + private final Runnable mSetVisibleEndRunnable = () -> { mKeyguardViewVisibilityAnimating = false; mView.setVisibility(View.VISIBLE); log("Callback Set Visibility to VISIBLE"); diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 235a8bca6d1e..5f2afe8f755d 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -294,6 +294,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme final CharSequence prevContentDescription = mView.getContentDescription(); if (mShowLockIcon) { + if (wasShowingFpIcon) { + // fp icon was shown by UdfpsView, and now we still want to animate the transition + // in this drawable + mView.updateIcon(ICON_FINGERPRINT, false); + } mView.updateIcon(ICON_LOCK, false); mView.setContentDescription(mLockedLabel); mView.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt b/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt deleted file mode 100644 index 2f03259766c0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt +++ /dev/null @@ -1,140 +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 - -import android.content.ComponentName -import android.content.Context -import android.content.Context.MODE_PRIVATE -import android.content.Intent -import android.content.SharedPreferences -import android.os.Bundle -import android.os.Environment -import android.os.storage.StorageManager -import android.util.Log -import androidx.core.util.Supplier -import com.android.internal.R -import com.android.systemui.broadcast.BroadcastSender -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import java.io.File -import javax.inject.Inject - -/** - * Performs a migration of pinned targets to the unbundled chooser if legacy data exists. - * - * Sends an explicit broadcast with the contents of the legacy pin preferences. The broadcast is - * protected by the RECEIVE_CHOOSER_PIN_MIGRATION permission. This class requires the - * ADD_CHOOSER_PINS permission in order to be able to send this broadcast. - */ -class ChooserPinMigration -@Inject -constructor( - private val context: Context, - private val featureFlags: FeatureFlags, - private val broadcastSender: BroadcastSender, - legacyPinPrefsFileSupplier: LegacyPinPrefsFileSupplier, -) : CoreStartable { - - private val legacyPinPrefsFile = legacyPinPrefsFileSupplier.get() - private val chooserComponent = - ComponentName.unflattenFromString( - context.resources.getString(R.string.config_chooserActivity) - ) - - override fun start() { - if (migrationIsRequired()) { - doMigration() - } - } - - private fun migrationIsRequired(): Boolean { - return featureFlags.isEnabled(Flags.CHOOSER_MIGRATION_ENABLED) && - legacyPinPrefsFile.exists() && - chooserComponent?.packageName != null - } - - private fun doMigration() { - Log.i(TAG, "Beginning migration") - - val legacyPinPrefs = context.getSharedPreferences(legacyPinPrefsFile, MODE_PRIVATE) - - if (legacyPinPrefs.all.isEmpty()) { - Log.i(TAG, "No data to migrate, deleting legacy file") - } else { - sendSharedPreferences(legacyPinPrefs) - Log.i(TAG, "Legacy data sent, deleting legacy preferences") - - val legacyPinPrefsEditor = legacyPinPrefs.edit() - legacyPinPrefsEditor.clear() - if (!legacyPinPrefsEditor.commit()) { - Log.e(TAG, "Failed to delete legacy preferences") - return - } - } - - if (!legacyPinPrefsFile.delete()) { - Log.e(TAG, "Legacy preferences deleted, but failed to delete legacy preferences file") - return - } - - Log.i(TAG, "Legacy preference deletion complete") - } - - private fun sendSharedPreferences(sharedPreferences: SharedPreferences) { - val bundle = Bundle() - - sharedPreferences.all.entries.forEach { (key, value) -> - when (value) { - is Boolean -> bundle.putBoolean(key, value) - else -> Log.e(TAG, "Unsupported preference type for $key: ${value?.javaClass}") - } - } - - sendBundle(bundle) - } - - private fun sendBundle(bundle: Bundle) { - val intent = - Intent().apply { - `package` = chooserComponent?.packageName!! - action = BROADCAST_ACTION - putExtras(bundle) - } - broadcastSender.sendBroadcast(intent, BROADCAST_PERMISSION) - } - - companion object { - private const val TAG = "PinnedShareTargetMigration" - private const val BROADCAST_ACTION = "android.intent.action.CHOOSER_PIN_MIGRATION" - private const val BROADCAST_PERMISSION = "android.permission.RECEIVE_CHOOSER_PIN_MIGRATION" - - class LegacyPinPrefsFileSupplier @Inject constructor(private val context: Context) : - Supplier<File> { - - override fun get(): File { - val packageDirectory = - Environment.getDataUserCePackageDirectory( - StorageManager.UUID_PRIVATE_INTERNAL, - context.userId, - context.packageName, - ) - val sharedPrefsDirectory = File(packageDirectory, "shared_prefs") - return File(sharedPrefsDirectory, "chooser_pin_settings.xml") - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt index 179eb391af4e..a3e7d71a92f6 100644 --- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt @@ -35,6 +35,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils import com.android.systemui.animation.Interpolators +import com.android.systemui.biometrics.AuthController import com.android.systemui.log.ScreenDecorationsLogger import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.util.asIndenting @@ -52,6 +53,7 @@ class FaceScanningOverlay( val keyguardUpdateMonitor: KeyguardUpdateMonitor, val mainExecutor: Executor, val logger: ScreenDecorationsLogger, + val authController: AuthController, ) : ScreenDecorations.DisplayCutoutView(context, pos) { private var showScanningAnim = false private val rimPaint = Paint() @@ -102,7 +104,9 @@ class FaceScanningOverlay( } override fun enableShowProtection(show: Boolean) { - val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show + val animationRequired = + keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing + val showScanningAnimNow = animationRequired && show if (showScanningAnimNow == showScanningAnim) { return } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 92344dbbfe15..09992290a9ad 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -1005,9 +1005,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered. */ public boolean isRearFpsSupported() { - for (FingerprintSensorPropertiesInternal prop: mFpProps) { - if (prop.sensorType == TYPE_REAR) { - return true; + if (mFpProps != null) { + for (FingerprintSensorPropertiesInternal prop: mFpProps) { + if (prop.sensorType == TYPE_REAR) { + return true; + } } } return false; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index f6b71336675f..691017b220f8 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -324,10 +324,6 @@ public class BrightLineFalsingManager implements FalsingManager { @Override public boolean isFalseLongTap(@Penalty int penalty) { - if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) { - return false; - } - checkDestroyed(); if (skipFalsing(GENERIC)) { diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt index 6a6c3eb05399..0869351e727b 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt @@ -16,10 +16,10 @@ package com.android.systemui.common.shared.model -import androidx.annotation.ColorRes +import androidx.annotation.AttrRes /** Models an icon with a specific tint. */ data class TintedIcon( val icon: Icon, - @ColorRes val tint: Int?, + @AttrRes val tint: Int?, ) diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt index bcc5932dcf30..5c5723fa0a5e 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt @@ -17,6 +17,7 @@ package com.android.systemui.common.ui.binder import android.widget.ImageView +import com.android.settingslib.Utils import com.android.systemui.common.shared.model.TintedIcon object TintedIconViewBinder { @@ -33,7 +34,7 @@ object TintedIconViewBinder { IconViewBinder.bind(tintedIcon.icon, view) view.imageTintList = if (tintedIcon.tint != null) { - view.resources.getColorStateList(tintedIcon.tint, view.context.theme) + Utils.getColorAttr(view.context, tintedIcon.tint) } else { null } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt index 26fc36dd3c9e..81ed07653f26 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt @@ -19,10 +19,18 @@ package com.android.systemui.common.ui.view import android.util.MathUtils import android.view.MotionEvent -/** Returns the distance from the position of this [MotionEvent] and the given coordinates. */ -fun MotionEvent.distanceFrom( - x: Float, - y: Float, +/** + * Returns the distance from the raw position of this [MotionEvent] and the given coordinates. + * Because this is all expected to be in the coordinate space of the display and not the view, + * applying mutations to the view (such as scaling animations) does not affect the distance + * measured. + * @param xOnDisplay the x coordinate relative to the display + * @param yOnDisplay the y coordinate relative to the display + * @return distance from the raw position of this [MotionEvent] and the given coordinates + */ +fun MotionEvent.rawDistanceFrom( + xOnDisplay: Float, + yOnDisplay: Float, ): Float { - return MathUtils.dist(this.x, this.y, x, y) + return MathUtils.dist(this.rawX, this.rawY, xOnDisplay, yOnDisplay) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index df236e7f9c8f..9bf6b2a5b42b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -17,7 +17,6 @@ package com.android.systemui.dagger import com.android.keyguard.KeyguardBiometricLockoutLogger -import com.android.systemui.ChooserPinMigration import com.android.systemui.ChooserSelector import com.android.systemui.CoreStartable import com.android.systemui.LatencyTester @@ -76,13 +75,6 @@ abstract class SystemUICoreStartableModule { @ClassKey(AuthController::class) abstract fun bindAuthController(service: AuthController): CoreStartable - /** Inject into ChooserPinMigration. */ - @Binds - @IntoMap - @ClassKey(ChooserPinMigration::class) - @PerUser - abstract fun bindChooserPinMigration(sysui: ChooserPinMigration): CoreStartable - /** Inject into ChooserCoreStartable. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index 88c0c50d09a5..4e62104034ee 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -98,7 +98,8 @@ class FaceScanningProviderFactory @Inject constructor( } fun shouldShowFaceScanningAnim(): Boolean { - return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning + return canShowFaceScanningAnim() && + (keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing) } } @@ -142,6 +143,7 @@ class FaceScanningOverlayProviderImpl( keyguardUpdateMonitor, mainExecutor, logger, + authController, ) view.id = viewId view.setColor(tintColor) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java index 7f44463f1191..aca621bd9e55 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java @@ -41,6 +41,7 @@ import com.google.common.util.concurrent.ListenableFuture; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -195,7 +196,14 @@ public class DreamOverlayTouchMonitor { * Called by the monitor when this session is removed. */ private void onRemoved() { - mCallbacks.forEach(callback -> callback.onRemoved()); + mEventListeners.clear(); + mGestureListeners.clear(); + final Iterator<Callback> iter = mCallbacks.iterator(); + while (iter.hasNext()) { + final Callback callback = iter.next(); + callback.onRemoved(); + iter.remove(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 6bb0f2ee48d6..f28aeadc7acb 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -65,7 +65,7 @@ object Flags { val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update") // TODO(b/254512538): Tracking Bug - val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply") + val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply") // TODO(b/254512425): Tracking Bug val NOTIFICATION_MEMORY_MONITOR_ENABLED = @@ -103,10 +103,10 @@ object Flags { val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD = releasedFlag(254647461, "filter_unseen_notifs_on_keyguard") - // TODO(b/263414400): Tracking Bug + // TODO(b/277338665): Tracking Bug @JvmField - val NOTIFICATION_ANIMATE_BIG_PICTURE = - releasedFlag(120, "notification_animate_big_picture") + val NOTIFICATION_SHELF_REFACTOR = + unreleasedFlag(271161129, "notification_shelf_refactor") @JvmField val ANIMATED_NOTIFICATION_SHADE_INSETS = @@ -406,7 +406,7 @@ object Flags { val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations") // TODO(b/270437894): Tracking Bug - val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume") + val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume", teamfood = true) // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") @@ -414,9 +414,6 @@ object Flags { // TODO(b/254512758): Tracking Bug @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple") - // TODO(b/270882464): Tracking Bug - val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2") - // TODO(b/265045965): Tracking Bug val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot") @@ -603,9 +600,6 @@ object Flags { // TODO(b/254512507): Tracking Bug val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled") - // TODO(b/266983432) Tracking Bug - val SHARESHEET_CUSTOM_ACTIONS = releasedFlag(1501, "sharesheet_custom_actions") - // TODO(b/266982749) Tracking Bug val SHARESHEET_RESELECTION_ACTION = releasedFlag(1502, "sharesheet_reselection_action") @@ -616,9 +610,6 @@ object Flags { val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW = releasedFlag(1504, "sharesheet_scrollable_image_preview") - // TODO(b/274137694) Tracking Bug - val CHOOSER_MIGRATION_ENABLED = unreleasedFlag(1505, "chooser_migration_enabled") - // 1700 - clipboard @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior") @@ -648,9 +639,6 @@ object Flags { val APP_PANELS_REMOVE_APPS_ALLOWED = unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = true) - // 2100 - Falsing Manager - @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps") - // 2200 - udfps // TODO(b/259264861): Tracking Bug @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 56e73980079d..0abce82527f9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -92,6 +92,9 @@ interface DeviceEntryFaceAuthRepository { /** Current state of whether face authentication is running. */ val isAuthRunning: Flow<Boolean> + /** Whether bypass is currently enabled */ + val isBypassEnabled: Flow<Boolean> + /** * Trigger face authentication. * @@ -166,7 +169,7 @@ constructor( override val isAuthenticated: Flow<Boolean> get() = _isAuthenticated - private val bypassEnabled: Flow<Boolean> = + override val isBypassEnabled: Flow<Boolean> = keyguardBypassController?.let { conflatedCallbackFlow { val callback = @@ -222,7 +225,7 @@ constructor( // & detection is supported & biometric unlock is not allowed. listOf( canFaceAuthOrDetectRun(), - logAndObserve(bypassEnabled, "bypassEnabled"), + logAndObserve(isBypassEnabled, "isBypassEnabled"), logAndObserve( biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(), "nonStrongBiometricIsNotAllowed" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt index 779095cd1d1e..5745d6ae077e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt @@ -26,7 +26,7 @@ import androidx.core.animation.CycleInterpolator import androidx.core.animation.ObjectAnimator import com.android.systemui.R import com.android.systemui.animation.Expandable -import com.android.systemui.common.ui.view.distanceFrom +import com.android.systemui.common.ui.view.rawDistanceFrom import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper @@ -41,14 +41,14 @@ class KeyguardQuickAffordanceOnTouchListener( private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong() private var longPressAnimator: ViewPropertyAnimator? = null - private val down: PointF by lazy { PointF() } + private val downDisplayCoords: PointF by lazy { PointF() } @SuppressLint("ClickableViewAccessibility") override fun onTouch(v: View, event: MotionEvent): Boolean { return when (event.actionMasked) { MotionEvent.ACTION_DOWN -> if (viewModel.configKey != null) { - down.set(event.x, event.y) + downDisplayCoords.set(event.rawX, event.rawY) if (isUsingAccurateTool(event)) { // For accurate tool types (stylus, mouse, etc.), we don't require a // long-press. @@ -81,7 +81,13 @@ class KeyguardQuickAffordanceOnTouchListener( if (!isUsingAccurateTool(event)) { // Moving too far while performing a long-press gesture cancels that // gesture. - if (event.distanceFrom(down.x, down.y) > ViewConfiguration.getTouchSlop()) { + if ( + event + .rawDistanceFrom( + downDisplayCoords.x, + downDisplayCoords.y, + ) > ViewConfiguration.getTouchSlop() + ) { cancel() } } @@ -94,7 +100,7 @@ class KeyguardQuickAffordanceOnTouchListener( // the pointer performs a click. if ( viewModel.configKey != null && - event.distanceFrom(down.x, down.y) <= + event.rawDistanceFrom(downDisplayCoords.x, downDisplayCoords.y) <= ViewConfiguration.getTouchSlop() && falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt index ad3fb637961b..c54203c97013 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt @@ -21,7 +21,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration import com.android.systemui.animation.view.LaunchableLinearLayout -import com.android.systemui.common.ui.view.distanceFrom +import com.android.systemui.common.ui.view.rawDistanceFrom import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel class KeyguardSettingsButtonOnTouchListener( @@ -29,18 +29,20 @@ class KeyguardSettingsButtonOnTouchListener( private val viewModel: KeyguardSettingsMenuViewModel, ) : View.OnTouchListener { - private val downPosition = PointF() + private val downPositionDisplayCoords = PointF() override fun onTouch(view: View, motionEvent: MotionEvent): Boolean { when (motionEvent.actionMasked) { MotionEvent.ACTION_DOWN -> { view.isPressed = true - downPosition.set(motionEvent.x, motionEvent.y) + downPositionDisplayCoords.set(motionEvent.rawX, motionEvent.rawY) viewModel.onTouchGestureStarted() } MotionEvent.ACTION_UP -> { view.isPressed = false - val distanceMoved = motionEvent.distanceFrom(downPosition.x, downPosition.y) + val distanceMoved = + motionEvent + .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y) val isClick = distanceMoved < ViewConfiguration.getTouchSlop() viewModel.onTouchGestureEnded(isClick) if (isClick) { diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt index 9d2d3553db6d..faaa205b15c2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt @@ -18,7 +18,7 @@ package com.android.systemui.log.table import android.os.Trace import com.android.systemui.Dumpable -import com.android.systemui.plugins.util.RingBuffer +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.text.SimpleDateFormat diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt index dbc2a5ec4e0a..b29b5887545a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -19,7 +19,7 @@ package com.android.systemui.media.taptotransfer.common import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import androidx.annotation.ColorRes +import androidx.annotation.AttrRes import androidx.annotation.DrawableRes import com.android.systemui.R import com.android.systemui.common.shared.model.ContentDescription @@ -108,7 +108,7 @@ class MediaTttUtils { data class IconInfo( val contentDescription: ContentDescription, val icon: MediaTttIcon, - @ColorRes val tint: Int?, + @AttrRes val tint: Int?, /** * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 26b0e8dd9895..b9ef916eebdf 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -55,6 +55,7 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.WindowInsets; import android.view.WindowManager; @@ -173,7 +174,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } }; - + private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); private final Context mContext; private final UserTracker mUserTracker; private final OverviewProxyService mOverviewProxyService; @@ -901,6 +902,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev); } + // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new + // ACTION_DOWN, in that case we should just reuse the old instance. + mVelocityTracker.clear(); + // Verify if this is in within the touch region and we aren't in immersive mode, and // either the bouncer is showing or the notification panel is hidden mInputEventReceiver.setBatchingEnabled(false); @@ -1027,11 +1032,30 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private void dispatchToBackAnimation(MotionEvent event) { if (mBackAnimation != null) { + mVelocityTracker.addMovement(event); + + final float velocityX; + final float velocityY; + if (event.getAction() == MotionEvent.ACTION_UP) { + // Compute the current velocity is expensive (see computeCurrentVelocity), so we + // are only doing it when the user completes the gesture. + int unitPixelPerSecond = 1000; + int maxVelocity = mViewConfiguration.getScaledMaximumFlingVelocity(); + mVelocityTracker.computeCurrentVelocity(unitPixelPerSecond, maxVelocity); + velocityX = mVelocityTracker.getXVelocity(); + velocityY = mVelocityTracker.getYVelocity(); + } else { + velocityX = Float.NaN; + velocityY = Float.NaN; + } + mBackAnimation.onBackMotion( - event.getX(), - event.getY(), - event.getActionMasked(), - mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); + /* touchX = */ event.getX(), + /* touchY = */ event.getY(), + /* velocityX = */ velocityX, + /* velocityY = */ velocityY, + /* keyAction = */ event.getActionMasked(), + /* swipeEdge = */ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); } } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c2c1306d2a32..a765702a95b2 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -18,6 +18,10 @@ package com.android.systemui.power; import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason; + import android.app.Dialog; import android.app.KeyguardManager; import android.app.Notification; @@ -691,7 +695,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { d.setTitle(R.string.battery_saver_confirmation_title); d.setPositiveButton(R.string.battery_saver_confirmation_ok, (dialog, which) -> { - setSaverMode(true, false); + setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION); logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK); }); d.setNegativeButton(android.R.string.cancel, (dialog, which) -> @@ -790,8 +794,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { return builder; } - private void setSaverMode(boolean mode, boolean needFirstTimeWarning) { - BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning); + private void setSaverMode(boolean mode, boolean needFirstTimeWarning, + @SaverManualEnabledReason int reason) { + BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason); } private void startBatterySaverSchedulePage() { @@ -839,7 +844,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } else if (action.equals(ACTION_START_SAVER)) { logEvent(BatteryWarningEvents .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON); - setSaverMode(true, true); + setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING); dismissLowBatteryNotification(); } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) { dismissLowBatteryNotification(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index ce690e239da0..856c64a52290 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -20,14 +20,10 @@ import android.content.res.Resources; import android.os.Build; import android.provider.Settings; -import com.android.internal.logging.InstanceId; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; -import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; -import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.util.leak.GarbageMonitor; import java.util.ArrayList; @@ -35,7 +31,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -public interface QSHost extends PanelInteractor, CustomTileAddedRepository { +public interface QSHost { String TILES_SETTING = Settings.Secure.QS_TILES; int POSITION_AT_END = -1; @@ -57,11 +53,9 @@ public interface QSHost extends PanelInteractor, CustomTileAddedRepository { return tiles; } - void warn(String message, Throwable t); Context getContext(); Context getUserContext(); int getUserId(); - UiEventLogger getUiEventLogger(); Collection<QSTile> getTiles(); void addCallback(Callback callback); void removeCallback(Callback callback); @@ -75,7 +69,11 @@ public interface QSHost extends PanelInteractor, CustomTileAddedRepository { * @see QSFactory#createTileView */ QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView); - /** Create a {@link QSTile} of a {@code tileSpec} type. */ + /** Create a {@link QSTile} of a {@code tileSpec} type. + * + * This should only be called by classes that need to create one-off instances of tiles. + * Do not use to create {@code custom} tiles without explicitly taking care of its lifecycle. + */ QSTile createTile(String tileSpec); /** @@ -105,8 +103,6 @@ public interface QSHost extends PanelInteractor, CustomTileAddedRepository { int indexOf(String tileSpec); - InstanceId getNewInstanceId(); - interface Callback { void onTilesChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt new file mode 100644 index 000000000000..67927a4153b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt @@ -0,0 +1,202 @@ +/* + * 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.qs + +import android.content.ComponentName +import android.content.Context +import androidx.annotation.GuardedBy +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.QSTileView +import com.android.systemui.qs.external.TileServiceRequestController +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** + * Adapter to determine what real class to use for classes that depend on [QSHost]. + * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost]. + * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be + * routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will still be routed to + * [QSTileHost]. + * + * This routing also includes dumps. + */ +@SysUISingleton +class QSHostAdapter +@Inject +constructor( + private val qsTileHost: QSTileHost, + private val interactor: CurrentTilesInteractor, + private val context: Context, + private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder, + @Application private val scope: CoroutineScope, + private val featureFlags: FeatureFlags, + dumpManager: DumpManager, +) : QSHost { + + companion object { + private const val TAG = "QSTileHost" + } + + private val useNewHost = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST) + + @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>() + + init { + scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() } + // Redirect dump to the correct host (needed for CTS tests) + dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost) + } + + override fun getTiles(): Collection<QSTile> { + return if (useNewHost) { + interactor.currentQSTiles + } else { + qsTileHost.getTiles() + } + } + + override fun getSpecs(): List<String> { + return if (useNewHost) { + interactor.currentTilesSpecs.map { it.spec } + } else { + qsTileHost.getSpecs() + } + } + + override fun removeTile(spec: String) { + if (useNewHost) { + interactor.removeTiles(listOf(TileSpec.create(spec))) + } else { + qsTileHost.removeTile(spec) + } + } + + override fun addCallback(callback: QSHost.Callback) { + if (useNewHost) { + val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } } + synchronized(callbacksMap) { callbacksMap.put(callback, job) } + } else { + qsTileHost.addCallback(callback) + } + } + + override fun removeCallback(callback: QSHost.Callback) { + if (useNewHost) { + synchronized(callbacksMap) { callbacksMap.get(callback)?.cancel() } + } else { + qsTileHost.removeCallback(callback) + } + } + + override fun removeTiles(specs: Collection<String>) { + if (useNewHost) { + interactor.removeTiles(specs.map(TileSpec::create)) + } else { + qsTileHost.removeTiles(specs) + } + } + + override fun removeTileByUser(component: ComponentName) { + if (useNewHost) { + interactor.removeTiles(listOf(TileSpec.create(component))) + } else { + qsTileHost.removeTileByUser(component) + } + } + + override fun addTile(spec: String, position: Int) { + if (useNewHost) { + interactor.addTile(TileSpec.create(spec), position) + } else { + qsTileHost.addTile(spec, position) + } + } + + override fun addTile(component: ComponentName, end: Boolean) { + if (useNewHost) { + interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0) + } else { + qsTileHost.addTile(component, end) + } + } + + override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) { + if (useNewHost) { + interactor.setTiles(newTiles.map(TileSpec::create)) + } else { + qsTileHost.changeTilesByUser(previousTiles, newTiles) + } + } + + override fun getContext(): Context { + return if (useNewHost) { + context + } else { + qsTileHost.context + } + } + + override fun getUserContext(): Context { + return if (useNewHost) { + interactor.userContext.value + } else { + qsTileHost.userContext + } + } + + override fun getUserId(): Int { + return if (useNewHost) { + interactor.userId.value + } else { + qsTileHost.userId + } + } + + override fun createTileView( + themedContext: Context?, + tile: QSTile?, + collapsedView: Boolean + ): QSTileView { + return qsTileHost.createTileView(themedContext, tile, collapsedView) + } + + override fun createTile(tileSpec: String): QSTile? { + return qsTileHost.createTile(tileSpec) + } + + override fun addTile(spec: String) { + return addTile(spec, QSHost.POSITION_AT_END) + } + + override fun addTile(tile: ComponentName) { + return addTile(tile, false) + } + + override fun indexOf(tileSpec: String): Int { + return specs.indexOf(tileSpec) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 8bbdeeda356c..59b94b7c4bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -29,16 +29,14 @@ import androidx.annotation.MainThread; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.InstanceId; -import com.android.internal.logging.InstanceIdSequence; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dumpable; import com.android.systemui.ProtoDumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.nano.SystemUIProtoDump; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.qs.QSFactory; @@ -48,9 +46,10 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServiceKey; -import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.nano.QsTileState; +import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.settings.UserFileManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.AutoTileManager; @@ -85,10 +84,10 @@ import javax.inject.Provider; * This class also provides the interface for adding/removing/changing tiles. */ @SysUISingleton -public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable { +public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable, + PanelInteractor, CustomTileAddedRepository { private static final String TAG = "QSTileHost"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final int MAX_QS_INSTANCE_ID = 1 << 20; // Shared prefs that hold tile lifecycle info. @VisibleForTesting @@ -99,10 +98,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P private final ArrayList<String> mTileSpecs = new ArrayList<>(); private final TunerService mTunerService; private final PluginManager mPluginManager; - private final DumpManager mDumpManager; private final QSLogger mQSLogger; - private final UiEventLogger mUiEventLogger; - private final InstanceIdSequence mInstanceIdSequence; private final CustomTileStatePersister mCustomTileStatePersister; private final Executor mMainExecutor; private final UserFileManager mUserFileManager; @@ -122,9 +118,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged private boolean mTilesListDirty = true; - private final TileServiceRequestController mTileServiceRequestController; private TileLifecycleManager.Factory mTileLifeCycleManagerFactory; + private final FeatureFlags mFeatureFlags; + @Inject public QSTileHost(Context context, QSFactory defaultFactory, @@ -132,35 +129,29 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, - DumpManager dumpManager, Optional<CentralSurfaces> centralSurfacesOptional, QSLogger qsLogger, - UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, - TileServiceRequestController.Builder tileServiceRequestControllerBuilder, TileLifecycleManager.Factory tileLifecycleManagerFactory, - UserFileManager userFileManager + UserFileManager userFileManager, + FeatureFlags featureFlags ) { mContext = context; mUserContext = context; mTunerService = tunerService; mPluginManager = pluginManager; - mDumpManager = dumpManager; mQSLogger = qsLogger; - mUiEventLogger = uiEventLogger; mMainExecutor = mainExecutor; - mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this); mTileLifeCycleManagerFactory = tileLifecycleManagerFactory; mUserFileManager = userFileManager; + mFeatureFlags = featureFlags; - mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID); mCentralSurfacesOptional = centralSurfacesOptional; mQsFactories.add(defaultFactory); pluginManager.addPluginListener(this, QSFactory.class, true); - mDumpManager.registerDumpable(TAG, this); mUserTracker = userTracker; mSecureSettings = secureSettings; mCustomTileStatePersister = customTileStatePersister; @@ -172,22 +163,14 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P tunerService.addTunable(this, TILES_SETTING); // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. mAutoTiles = autoTiles.get(); - mTileServiceRequestController.init(); }); } - @Override - public InstanceId getNewInstanceId() { - return mInstanceIdSequence.newInstanceId(); - } - public void destroy() { mTiles.values().forEach(tile -> tile.destroy()); mAutoTiles.destroy(); mTunerService.removeTunable(this); mPluginManager.removePluginListener(this); - mDumpManager.unregisterDumpable(TAG); - mTileServiceRequestController.destroy(); } @Override @@ -210,11 +193,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P } @Override - public UiEventLogger getUiEventLogger() { - return mUiEventLogger; - } - - @Override public void addCallback(Callback callback) { mCallbacks.add(callback); } @@ -230,11 +208,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P } @Override - public void warn(String message, Throwable t) { - // already logged - } - - @Override public void collapsePanels() { mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels); } @@ -300,6 +273,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P if (!TILES_SETTING.equals(key)) { return; } + // Do not process tiles if the flag is enabled. + if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { + return; + } if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt new file mode 100644 index 000000000000..fc739edc69d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt @@ -0,0 +1,44 @@ +/* + * 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.qs + +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +interface QsEventLogger : UiEventLogger { + fun getNewInstanceId(): InstanceId +} + +@SysUISingleton +class QsEventLoggerImpl +@Inject +constructor( + uiEventLogger: UiEventLogger, +) : QsEventLogger, UiEventLogger by uiEventLogger { + + companion object { + private const val MAX_QS_INSTANCE_ID = 1 shl 20 + } + + val sequence = InstanceIdSequence(MAX_QS_INSTANCE_ID) + override fun getNewInstanceId(): InstanceId { + return sequence.newInstanceId() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt index 964fe7104324..1f63f5da2f2b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt @@ -19,7 +19,10 @@ package com.android.systemui.qs.dagger import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QSHostAdapter import com.android.systemui.qs.QSTileHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.QsEventLoggerImpl import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor @@ -31,15 +34,19 @@ import dagger.Provides @Module interface QSHostModule { - @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost + @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost + + @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger @Module companion object { + private const val MAX_QS_INSTANCE_ID = 1 shl 20 + @Provides @JvmStatic fun providePanelInteractor( featureFlags: FeatureFlags, - qsHost: QSHost, + qsHost: QSTileHost, panelInteractorImpl: PanelInteractorImpl ): PanelInteractor { return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { @@ -53,7 +60,7 @@ interface QSHostModule { @JvmStatic fun provideCustomTileAddedRepository( featureFlags: FeatureFlags, - qsHost: QSHost, + qsHost: QSTileHost, customTileAddedRepository: CustomTileAddedSharedPrefsRepository ): CustomTileAddedRepository { return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index d4854e1a7daf..897b0e73dca0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -59,17 +59,20 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.DisplayTracker; +import dagger.Lazy; + import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; -import dagger.Lazy; + public class CustomTile extends QSTileImpl<State> implements TileChangeListener { public static final String PREFIX = "custom("; @@ -111,6 +114,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private CustomTile( QSHost host, + QsEventLogger uiEventLogger, Looper backgroundLooper, Handler mainHandler, FalsingManager falsingManager, @@ -124,7 +128,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener TileServices tileServices, DisplayTracker displayTracker ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mTileServices = tileServices; mWindowManager = WindowManagerGlobal.getWindowManagerService(); @@ -561,6 +565,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener public static class Builder { final Lazy<QSHost> mQSHostLazy; + final QsEventLogger mUiEventLogger; final Looper mBackgroundLooper; final Handler mMainHandler; private final FalsingManager mFalsingManager; @@ -578,6 +583,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Inject public Builder( Lazy<QSHost> hostLazy, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -590,6 +596,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener DisplayTracker displayTracker ) { mQSHostLazy = hostLazy; + mUiEventLogger = uiEventLogger; mBackgroundLooper = backgroundLooper; mMainHandler = mainHandler; mFalsingManager = falsingManager; @@ -620,6 +627,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener String action = getAction(mSpec); return new CustomTile( mQSHostLazy.get(), + mUiEventLogger, mBackgroundLooper, mMainHandler, mFalsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index 00f0a67dbe22..e212bc4e7f5d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt @@ -22,6 +22,8 @@ import com.android.systemui.log.LogBufferFactory import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import dagger.Binds @@ -38,6 +40,11 @@ abstract class QSPipelineModule { abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository @Binds + abstract fun bindCurrentTilesInteractor( + impl: CurrentTilesInteractorImpl + ): CurrentTilesInteractor + + @Binds @IntoMap @ClassKey(PrototypeCoreStartable::class) abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt index d254e1b3d0d7..595b29a9dcb8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt @@ -32,6 +32,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -53,6 +54,8 @@ interface TileSpecRepository { * at the end of the list. * * Passing [TileSpec.Invalid] is a noop. + * + * Trying to add a tile beyond the end of the list will add it at the end. */ suspend fun addTile(@UserIdInt userId: Int, tile: TileSpec, position: Int = POSITION_AT_END) @@ -61,7 +64,7 @@ interface TileSpecRepository { * * Passing [TileSpec.Invalid] or a non present tile is a noop. */ - suspend fun removeTile(@UserIdInt userId: Int, tile: TileSpec) + suspend fun removeTiles(@UserIdInt userId: Int, tiles: Collection<TileSpec>) /** * Sets the list of current [tiles] for a given [userId]. @@ -106,6 +109,7 @@ constructor( } .onStart { emit(Unit) } .map { secureSettings.getStringForUser(SETTING, userId) ?: "" } + .distinctUntilChanged() .onEach { logger.logTilesChangedInSettings(it, userId) } .map { parseTileSpecs(it, userId) } .flowOn(backgroundDispatcher) @@ -117,7 +121,7 @@ constructor( } val tilesList = loadTiles(userId).toMutableList() if (tile !in tilesList) { - if (position < 0) { + if (position < 0 || position >= tilesList.size) { tilesList.add(tile) } else { tilesList.add(position, tile) @@ -126,12 +130,12 @@ constructor( } } - override suspend fun removeTile(userId: Int, tile: TileSpec) { - if (tile == TileSpec.Invalid) { + override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) { + if (tiles.all { it == TileSpec.Invalid }) { return } val tilesList = loadTiles(userId).toMutableList() - if (tilesList.remove(tile)) { + if (tilesList.removeAll(tiles)) { storeTiles(userId, tilesList.toList()) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt new file mode 100644 index 000000000000..91c6e8b5fcb6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -0,0 +1,344 @@ +/* + * 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.qs.pipeline.domain.interactor + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import com.android.systemui.Dumpable +import com.android.systemui.ProtoDumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.nano.SystemUIProtoDump +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.external.CustomTile +import com.android.systemui.qs.external.CustomTileStatePersister +import com.android.systemui.qs.external.TileLifecycleManager +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.qs.pipeline.domain.model.TileModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.qs.toProto +import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.kotlin.pairwise +import java.io.PrintWriter +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Interactor for retrieving the list of current QS tiles, as well as making changes to this list + * + * It is [ProtoDumpable] as it needs to be able to dump state for CTS tests. + */ +interface CurrentTilesInteractor : ProtoDumpable { + /** Current list of tiles with their corresponding spec. */ + val currentTiles: StateFlow<List<TileModel>> + + /** User for the [currentTiles]. */ + val userId: StateFlow<Int> + + /** [Context] corresponding to [userId] */ + val userContext: StateFlow<Context> + + /** List of specs corresponding to the last value of [currentTiles] */ + val currentTilesSpecs: List<TileSpec> + get() = currentTiles.value.map(TileModel::spec) + + /** List of tiles corresponding to the last value of [currentTiles] */ + val currentQSTiles: List<QSTile> + get() = currentTiles.value.map(TileModel::tile) + + /** + * Requests that a tile be added in the list of tiles for the current user. + * + * @see TileSpecRepository.addTile + */ + fun addTile(spec: TileSpec, position: Int = TileSpecRepository.POSITION_AT_END) + + /** + * Requests that tiles be removed from the list of tiles for the current user + * + * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and + * marked as removed. + * + * @see TileSpecRepository.removeTiles + */ + fun removeTiles(specs: Collection<TileSpec>) + + /** + * Requests that the list of tiles for the current user is changed to [specs]. + * + * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and + * marked as removed. + * + * @see TileSpecRepository.setTiles + */ + fun setTiles(specs: List<TileSpec>) +} + +/** + * This implementation of [CurrentTilesInteractor] will try to re-use existing [QSTile] objects when + * possible, in particular: + * * It will only destroy tiles when they are not part of the list of tiles anymore + * * Platform tiles will be kept between users, with a call to [QSTile.userSwitch] + * * [CustomTile]s will only be destroyed if the user changes. + */ +@SysUISingleton +class CurrentTilesInteractorImpl +@Inject +constructor( + private val tileSpecRepository: TileSpecRepository, + private val userRepository: UserRepository, + private val customTileStatePersister: CustomTileStatePersister, + private val tileFactory: QSFactory, + private val customTileAddedRepository: CustomTileAddedRepository, + private val tileLifecycleManagerFactory: TileLifecycleManager.Factory, + private val userTracker: UserTracker, + @Main private val mainDispatcher: CoroutineDispatcher, + @Background private val backgroundDispatcher: CoroutineDispatcher, + @Application private val scope: CoroutineScope, + private val logger: QSPipelineLogger, + featureFlags: FeatureFlags, +) : CurrentTilesInteractor { + + private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> = + MutableStateFlow(emptyList()) + + override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow() + + // This variable should only be accessed inside the collect of `startTileCollection`. + private val specsToTiles = mutableMapOf<TileSpec, QSTile>() + + private val currentUser = MutableStateFlow(userTracker.userId) + override val userId = currentUser.asStateFlow() + + private val _userContext = MutableStateFlow(userTracker.userContext) + override val userContext = _userContext.asStateFlow() + + init { + if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { + startTileCollection() + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun startTileCollection() { + scope.launch { + userRepository.selectedUserInfo + .flatMapLatest { user -> + currentUser.value = user.id + _userContext.value = userTracker.userContext + tileSpecRepository.tilesSpecs(user.id).map { user.id to it } + } + .distinctUntilChanged() + .pairwise(-1 to emptyList()) + .flowOn(backgroundDispatcher) + .collect { (old, new) -> + val newTileList = new.second + val userChanged = old.first != new.first + val newUser = new.first + + // Destroy all tiles that are not in the new set + specsToTiles + .filter { it.key !in newTileList } + .forEach { entry -> + logger.logTileDestroyed( + entry.key, + if (userChanged) { + QSPipelineLogger.TileDestroyedReason + .TILE_NOT_PRESENT_IN_NEW_USER + } else { + QSPipelineLogger.TileDestroyedReason.TILE_REMOVED + } + ) + entry.value.destroy() + } + // MutableMap will keep the insertion order + val newTileMap = mutableMapOf<TileSpec, QSTile>() + + newTileList.forEach { tileSpec -> + if (tileSpec !in newTileMap) { + val newTile = + if (tileSpec in specsToTiles) { + processExistingTile( + tileSpec, + specsToTiles.getValue(tileSpec), + userChanged, + newUser + ) + ?: createTile(tileSpec) + } else { + createTile(tileSpec) + } + if (newTile != null) { + newTileMap[tileSpec] = newTile + } + } + } + + val resolvedSpecs = newTileMap.keys.toList() + specsToTiles.clear() + specsToTiles.putAll(newTileMap) + _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) } + if (resolvedSpecs != newTileList) { + // There were some tiles that couldn't be created. Change the value in the + // repository + launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) } + } + } + } + } + + override fun addTile(spec: TileSpec, position: Int) { + scope.launch { + tileSpecRepository.addTile(userRepository.getSelectedUserInfo().id, spec, position) + } + } + + override fun removeTiles(specs: Collection<TileSpec>) { + val currentSpecsCopy = currentTilesSpecs.toSet() + val user = currentUser.value + // intersect: tiles that are there and are being removed + val toFree = currentSpecsCopy.intersect(specs).filterIsInstance<TileSpec.CustomTileSpec>() + toFree.forEach { onCustomTileRemoved(it.componentName, user) } + if (currentSpecsCopy.intersect(specs).isNotEmpty()) { + // We don't want to do the call to set in case getCurrentTileSpecs is not the most + // up to date for this user. + scope.launch { tileSpecRepository.removeTiles(user, specs) } + } + } + + override fun setTiles(specs: List<TileSpec>) { + val currentSpecsCopy = currentTilesSpecs + val user = currentUser.value + if (currentSpecsCopy != specs) { + // minus: tiles that were there but are not there anymore + val toFree = currentSpecsCopy.minus(specs).filterIsInstance<TileSpec.CustomTileSpec>() + toFree.forEach { onCustomTileRemoved(it.componentName, user) } + scope.launch { tileSpecRepository.setTiles(user, specs) } + } + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("CurrentTileInteractorImpl:") + pw.println("User: ${userId.value}") + currentTiles.value + .map { it.tile } + .filterIsInstance<Dumpable>() + .forEach { it.dump(pw, args) } + } + + override fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>) { + val data = + currentTiles.value.map { it.tile.state }.mapNotNull { it.toProto() }.toTypedArray() + systemUIProtoDump.tiles = data + } + + private fun onCustomTileRemoved(componentName: ComponentName, userId: Int) { + val intent = Intent().setComponent(componentName) + val lifecycleManager = tileLifecycleManagerFactory.create(intent, UserHandle.of(userId)) + lifecycleManager.onStopListening() + lifecycleManager.onTileRemoved() + customTileStatePersister.removeState(TileServiceKey(componentName, userId)) + customTileAddedRepository.setTileAdded(componentName, userId, false) + lifecycleManager.flushMessagesAndUnbind() + } + + private suspend fun createTile(spec: TileSpec): QSTile? { + val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) } + if (tile == null) { + logger.logTileNotFoundInFactory(spec) + return null + } else { + tile.tileSpec = spec.spec + return if (!tile.isAvailable) { + logger.logTileDestroyed( + spec, + QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE, + ) + tile.destroy() + null + } else { + logger.logTileCreated(spec) + tile + } + } + } + + private fun processExistingTile( + tileSpec: TileSpec, + qsTile: QSTile, + userChanged: Boolean, + user: Int, + ): QSTile? { + return when { + !qsTile.isAvailable -> { + logger.logTileDestroyed( + tileSpec, + QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE + ) + qsTile.destroy() + null + } + // Tile is in the current list of tiles and available. + // We have a handful of different cases + qsTile !is CustomTile -> { + // The tile is not a custom tile. Make sure they are reset to the correct user + qsTile.removeCallbacks() + if (userChanged) { + qsTile.userSwitch(user) + logger.logTileUserChanged(tileSpec, user) + } + qsTile + } + qsTile.user == user -> { + // The tile is a custom tile for the same user, just return it + qsTile.removeCallbacks() + qsTile + } + else -> { + // The tile is a custom tile and the user has changed. Destroy it + qsTile.destroy() + logger.logTileDestroyed( + tileSpec, + QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED + ) + null + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt new file mode 100644 index 000000000000..e2381ecd8b97 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt @@ -0,0 +1,32 @@ +/* + * 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.qs.pipeline.domain.model + +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.pipeline.shared.TileSpec + +/** + * Container for a [tile] and its [spec]. The following must be true: + * ``` + * spec.spec == tile.tileSpec + * ``` + */ +data class TileModel(val spec: TileSpec, val tile: QSTile) { + init { + check(spec.spec == tile.tileSpec) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt index 69d8248a11f5..89408006c300 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt @@ -93,7 +93,7 @@ constructor( private fun performRemove(args: List<String>, spec: TileSpec) { val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id - scope.launch { tileSpecRepository.removeTile(user, spec) } + scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) } } override fun help(pw: PrintWriter) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt index c691c2f668ad..af1cd0995a21 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt @@ -66,6 +66,10 @@ sealed class TileSpec private constructor(open val spec: String) { } } + fun create(component: ComponentName): CustomTileSpec { + return CustomTileSpec(CustomTile.toSpec(component), component) + } + private val String.isCustomTileSpec: Boolean get() = startsWith(CustomTile.PREFIX) diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt index 200f7431e906..767ce919d027 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt @@ -73,4 +73,59 @@ constructor( { "Tiles changed in settings for user $int1: $str1" } ) } + + /** Log when a tile is destroyed and its reason for destroying. */ + fun logTileDestroyed(spec: TileSpec, reason: TileDestroyedReason) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.DEBUG, + { + str1 = spec.toString() + str2 = reason.readable + }, + { "Tile $str1 destroyed. Reason: $str2" } + ) + } + + /** Log when a tile is created. */ + fun logTileCreated(spec: TileSpec) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.DEBUG, + { str1 = spec.toString() }, + { "Tile $str1 created" } + ) + } + + /** Ĺog when trying to create a tile, but it's not found in the factory. */ + fun logTileNotFoundInFactory(spec: TileSpec) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.VERBOSE, + { str1 = spec.toString() }, + { "Tile $str1 not found in factory" } + ) + } + + /** Log when the user is changed for a platform tile. */ + fun logTileUserChanged(spec: TileSpec, user: Int) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.VERBOSE, + { + str1 = spec.toString() + int1 = user + }, + { "User changed to $int1 for tile $str1" } + ) + } + + /** Reasons for destroying an existing tile. */ + enum class TileDestroyedReason(val readable: String) { + TILE_REMOVED("Tile removed from current set"), + CUSTOM_TILE_USER_CHANGED("User changed for custom tile"), + NEW_TILE_NOT_AVAILABLE("New tile not available"), + EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"), + TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"), + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 49ba5086f64d..2a9e7d05c187 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -66,6 +66,7 @@ import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSEvent; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SideLabelTileLayout; import com.android.systemui.qs.logging.QSLogger; @@ -179,6 +180,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy protected QSTileImpl( QSHost host, + QsEventLogger uiEventLogger, Looper backgroundLooper, Handler mainHandler, FalsingManager falsingManager, @@ -189,8 +191,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy ) { mHost = host; mContext = host.getContext(); - mInstanceId = host.getNewInstanceId(); - mUiEventLogger = host.getUiEventLogger(); + mInstanceId = uiEventLogger.getNewInstanceId(); + mUiEventLogger = uiEventLogger; mUiHandler = mainHandler; mHandler = new H(backgroundLooper); @@ -633,7 +635,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } catch (Throwable t) { final String error = "Error in " + name; Log.w(TAG, error, t); - mHost.warn(error, t); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 92a83bba8a68..30765f7f974d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -45,15 +45,18 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.GlobalSettings; +import dagger.Lazy; + import javax.inject.Inject; -import dagger.Lazy; + /** Quick settings tile: Airplane mode **/ public class AirplaneModeTile extends QSTileImpl<BooleanState> { @@ -69,6 +72,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { @Inject public AirplaneModeTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -81,7 +85,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { GlobalSettings globalSettings, UserTracker userTracker ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBroadcastDispatcher = broadcastDispatcher; mLazyConnectivityManager = lazyConnectivityManager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index 2ca452e45ecf..c709969ec6e2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -22,6 +22,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.settings.UserTracker @@ -31,6 +32,7 @@ import javax.inject.Inject class AlarmTile @Inject constructor( host: QSHost, + uiEventLogger: QsEventLogger, @Background backgroundLooper: Looper, @Main mainHandler: Handler, falsingManager: FalsingManager, @@ -42,6 +44,7 @@ class AlarmTile @Inject constructor( nextAlarmController: NextAlarmController ) : QSTileImpl<QSTile.State>( host, + uiEventLogger, backgroundLooper, mainHandler, falsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java index 027a464251c9..a444e7631527 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -62,6 +63,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements @Inject public BatterySaverTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -72,7 +74,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements BatteryController batteryController, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBatteryController = batteryController; mBatteryController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 08fe2709b810..30218a6a3180 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -47,6 +47,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BluetoothController; @@ -74,6 +75,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Inject public BluetoothTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -83,7 +85,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, BluetoothController bluetoothController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = bluetoothController; mController.observe(getLifecycle(), mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index 93e5f1efbdc8..65ef6b9c31a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -48,7 +49,9 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { public static final String TILE_SPEC = "cameratoggle"; @Inject - protected CameraToggleTile(QSHost host, + protected CameraToggleTile( + QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, MetricsLogger metricsLogger, @@ -58,7 +61,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger, sensorPrivacyController, keyguardStateController); } 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 8d984817ba77..54376fe604df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -47,6 +47,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.connectivity.NetworkController; @@ -84,6 +85,7 @@ public class CastTile extends QSTileImpl<BooleanState> { @Inject public CastTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -97,7 +99,7 @@ public class CastTile extends QSTileImpl<BooleanState> { HotspotController hotspotController, DialogLaunchAnimator dialogLaunchAnimator ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = castController; mKeyguard = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java index b6205d5df63d..cf9e3468c8f5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -56,6 +57,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { @Inject public ColorCorrectionTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -66,7 +68,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { UserTracker userTracker, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSetting = new SettingObserver(secureSettings, mHandler, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index 9a44e83ad9a1..4ecde6123dd5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -55,6 +56,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { @Inject public ColorInversionTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -65,7 +67,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { UserTracker userTracker, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSetting = new SettingObserver(secureSettings, mHandler, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index add517e18516..e769b5ef9989 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -58,6 +59,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements @Inject public DataSaverTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -68,7 +70,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements DataSaverController dataSaverController, DialogLaunchAnimator dialogLaunchAnimator ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mDataSaverController = dataSaverController; mDialogLaunchAnimator = dialogLaunchAnimator; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 01164fb0a009..ddaff3bb9a64 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -40,6 +40,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import java.util.concurrent.atomic.AtomicBoolean @@ -47,6 +48,7 @@ import javax.inject.Inject class DeviceControlsTile @Inject constructor( host: QSHost, + uiEventLogger: QsEventLogger, @Background backgroundLooper: Looper, @Main mainHandler: Handler, falsingManager: FalsingManager, @@ -56,14 +58,15 @@ class DeviceControlsTile @Inject constructor( qsLogger: QSLogger, private val controlsComponent: ControlsComponent ) : QSTileImpl<QSTile.State>( - host, - backgroundLooper, - mainHandler, - falsingManager, - metricsLogger, - statusBarStateController, - activityStarter, - qsLogger + host, + uiEventLogger, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger ) { private var hasControlsApps = AtomicBoolean(false) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 434fe45f47c0..3e7bdd1a1444 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -54,6 +54,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -88,6 +89,7 @@ public class DndTile extends QSTileImpl<BooleanState> { @Inject public DndTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -100,7 +102,7 @@ public class DndTile extends QSTileImpl<BooleanState> { SecureSettings secureSettings, DialogLaunchAnimator dialogLaunchAnimator ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = zenModeController; mSharedPreferences = sharedPreferences; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java index f913326a6a67..eef4c1dd4436 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java @@ -48,6 +48,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -90,6 +91,7 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { @Inject public DreamTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -105,7 +107,7 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER) boolean dreamOnlyEnabledForDockUser ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mDreamManager = dreamManager; mBroadcastDispatcher = broadcastDispatcher; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index e091a750fbec..2c986da8370a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.FlashlightController; @@ -55,6 +56,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements @Inject public FlashlightTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -64,7 +66,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements QSLogger qsLogger, FlashlightController flashlightController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mFlashlightController = flashlightController; mFlashlightController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt index 3f514344cee1..12d98473ff07 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt @@ -36,6 +36,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.phone.SystemUIDialog @@ -47,6 +48,7 @@ class FontScalingTile @Inject constructor( host: QSHost, + uiEventLogger: QsEventLogger, @Background backgroundLooper: Looper, @Main mainHandler: Handler, falsingManager: FalsingManager, @@ -61,6 +63,7 @@ constructor( ) : QSTileImpl<QSTile.State?>( host, + uiEventLogger, backgroundLooper, mainHandler, falsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 6bf8b7666054..4c3699ced6e5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -41,6 +41,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.DataSaverController; @@ -61,6 +62,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { @Inject public HotspotTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -71,7 +73,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { HotspotController hotspotController, DataSaverController dataSaverController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mHotspotController = hotspotController; mDataSaverController = dataSaverController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 75d01723667d..f16f0dcc5dba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -51,6 +51,7 @@ import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.AlphaControlledSignalTileView; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; @@ -90,6 +91,7 @@ public class InternetTile extends QSTileImpl<SignalState> { @Inject public InternetTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -101,7 +103,7 @@ public class InternetTile extends QSTileImpl<SignalState> { AccessPointController accessPointController, InternetDialogFactory internetDialogFactory ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mInternetDialogFactory = internetDialogFactory; mHandler = mainHandler; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index 27f58269722a..83c568878c94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -59,6 +60,7 @@ public class LocationTile extends QSTileImpl<BooleanState> { @Inject public LocationTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -70,7 +72,7 @@ public class LocationTile extends QSTileImpl<BooleanState> { KeyguardStateController keyguardStateController, PanelInteractor panelInteractor ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = locationController; mKeyguard = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index 2e475d40d55f..86a6a8c6a2b6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -48,7 +49,9 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { public static final String TILE_SPEC = "mictoggle"; @Inject - protected MicrophoneToggleTile(QSHost host, + protected MicrophoneToggleTile( + QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, MetricsLogger metricsLogger, @@ -58,7 +61,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger, sensorPrivacyController, keyguardStateController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java index e189f80a7c23..29ccb76de820 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java @@ -43,6 +43,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -65,6 +66,7 @@ public class NfcTile extends QSTileImpl<BooleanState> { @Inject public NfcTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -74,7 +76,7 @@ public class NfcTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, BroadcastDispatcher broadcastDispatcher ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBroadcastDispatcher = broadcastDispatcher; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index aacd53bde51c..405e1394871c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -45,6 +45,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.LocationController; @@ -80,6 +81,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements @Inject public NightDisplayTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -91,7 +93,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements ColorDisplayManager colorDisplayManager, NightDisplayListenerModule.Builder nightDisplayListenerBuilder ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mLocationController = locationController; mManager = colorDisplayManager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java index ae67d99ea2fb..1eb317aaeae8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java @@ -36,6 +36,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -57,6 +58,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { @Inject public OneHandedModeTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -66,7 +68,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, UserTracker userTracker, SecureSettings secureSettings) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSetting = new SettingObserver(secureSettings, mHandler, Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java index 92f52724ca0c..9e365d34bed9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -62,6 +63,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { @Inject public QRCodeScannerTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -70,7 +72,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { ActivityStarter activityStarter, QSLogger qsLogger, QRCodeScannerController qrCodeScannerController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mQRCodeScannerController = qrCodeScannerController; mQRCodeScannerController.observe(getLifecycle(), mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 4a3c56328006..e026bdbc2e08 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -49,6 +49,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -83,6 +84,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { @Inject public QuickAccessWalletTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -94,7 +96,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { PackageManager packageManager, SecureSettings secureSettings, QuickAccessWalletController quickAccessWalletController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = quickAccessWalletController; mKeyguardStateController = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index 10f1ce4946c8..2e04afb0048a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -59,6 +60,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> @Named(RBC_AVAILABLE) boolean isAvailable, ReduceBrightColorsController reduceBrightColorsController, QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -67,7 +69,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> ActivityStarter activityStarter, QSLogger qsLogger ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mReduceBrightColorsController = reduceBrightColorsController; mReduceBrightColorsController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 8888c733c3c1..7f7f8ad6a4c1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -44,6 +44,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -71,6 +72,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements @Inject public RotationLockTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -83,7 +85,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements BatteryController batteryController, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = rotationLockController; mController.observe(this, mCallback); 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 65592a717565..2d4652db6b80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -41,6 +41,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -74,6 +75,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> @Inject public ScreenRecordTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -88,7 +90,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> DialogLaunchAnimator dialogLaunchAnimator, PanelInteractor panelInteractor ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = controller; mController.observe(this, mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index d99c1d1daf7e..7c4f097a1724 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; @@ -68,7 +69,9 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS */ public abstract String getRestriction(); - protected SensorPrivacyToggleTile(QSHost host, + protected SensorPrivacyToggleTile( + QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -78,7 +81,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSensorPrivacyController = sensorPrivacyController; mKeyguard = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index 809689cb806d..a60d1ad448b4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BatteryController; @@ -69,6 +70,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements @Inject public UiModeNightTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -80,7 +82,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements BatteryController batteryController, LocationController locationController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBatteryController = batteryController; mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index 6a5c99032457..17e72e597e58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -40,6 +40,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.phone.ManagedProfileController; @@ -59,6 +60,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements @Inject public WorkModeTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -68,7 +70,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements QSLogger qsLogger, ManagedProfileController managedProfileController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mProfileController = managedProfileController; mProfileController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 72286f175671..3711a2f39b7b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -162,7 +162,7 @@ open class UserTrackerImpl internal constructor( private fun registerUserSwitchObserver() { iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() { override fun onBeforeUserSwitching(newUserId: Int) { - setUserIdInternal(newUserId) + handleBeforeUserSwitching(newUserId) } override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { @@ -180,6 +180,10 @@ open class UserTrackerImpl internal constructor( }, TAG) } + protected open fun handleBeforeUserSwitching(newUserId: Int) { + setUserIdInternal(newUserId) + } + @WorkerThread protected open fun handleUserSwitching(newUserId: Int) { Assert.isNotMainThread() diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt index 754036d3baa9..b8bd95c89ec8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt @@ -14,9 +14,9 @@ package com.android.systemui.shade import android.view.MotionEvent +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer import java.text.SimpleDateFormat import java.util.Locale diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 3316ca0c3fcd..222a0f4fa248 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -22,10 +22,6 @@ import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; -import static androidx.constraintlayout.widget.ConstraintSet.END; -import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; - -import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; @@ -73,12 +69,6 @@ import android.os.Trace; import android.os.UserManager; import android.os.VibrationEffect; import android.provider.Settings; -import android.transition.ChangeBounds; -import android.transition.Transition; -import android.transition.TransitionListenerAdapter; -import android.transition.TransitionManager; -import android.transition.TransitionSet; -import android.transition.TransitionValues; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; @@ -100,8 +90,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.FrameLayout; -import androidx.constraintlayout.widget.ConstraintSet; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; @@ -163,8 +151,6 @@ import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.ClockAnimations; -import com.android.systemui.plugins.ClockController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager.FalsingTapListener; import com.android.systemui.plugins.qs.QS; @@ -301,11 +287,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1); private static final Rect EMPTY_RECT = new Rect(); /** - * Duration to use for the animator when the keyguard status view alignment changes, and a - * custom clock animation is in use. - */ - private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000; - /** * Whether the Shade should animate to reflect Back gesture progress. * To minimize latency at runtime, we cache this, else we'd be reading it every time * updateQsExpansion() is called... and it's called very often. @@ -552,8 +533,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final KeyguardMediaController mKeyguardMediaController; - private boolean mStatusViewCentered = true; - private final Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition; private final Optional<NotificationPanelUnfoldAnimationController> mNotificationPanelUnfoldAnimationController; @@ -684,18 +663,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump step.getTransitionState() == TransitionState.RUNNING; }; - private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = - new TransitionListenerAdapter() { - @Override - public void onTransitionCancel(Transition transition) { - mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); - } - - @Override - public void onTransitionEnd(Transition transition) { - mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); - } - }; private final ActivityStarter mActivityStarter; @Inject @@ -1323,9 +1290,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate( R.layout.keyguard_status_view, mNotificationContainerParent, false); mNotificationContainerParent.addView(keyguardStatusView, statusIndex); - // When it's reinflated, this is centered by default. If it shouldn't be, this will update - // below when resources are updated. - mStatusViewCentered = true; attachSplitShadeMediaPlayerContainer( keyguardStatusView.findViewById(R.id.status_view_media_container)); @@ -1620,68 +1584,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updateKeyguardStatusViewAlignment(boolean animate) { boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); - if (mStatusViewCentered != shouldBeCentered) { - mStatusViewCentered = shouldBeCentered; - ConstraintSet constraintSet = new ConstraintSet(); - constraintSet.clone(mNotificationContainerParent); - int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline; - constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END); - if (animate) { - mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); - ChangeBounds transition = new ChangeBounds(); - if (mSplitShadeEnabled) { - // Excluding media from the transition on split-shade, as it doesn't transition - // horizontally properly. - transition.excludeTarget(R.id.status_view_media_container, true); - } - - transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - - ClockController clock = mKeyguardStatusViewController.getClockController(); - boolean customClockAnimation = clock != null - && clock.getConfig().getHasCustomPositionUpdatedAnimation(); - - if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { - // Find the clock, so we can exclude it from this transition. - FrameLayout clockContainerView = - mView.findViewById(R.id.lockscreen_clock_view_large); - - // The clock container can sometimes be null. If it is, just fall back to the - // old animation rather than setting up the custom animations. - if (clockContainerView == null || clockContainerView.getChildCount() == 0) { - transition.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition( - mNotificationContainerParent, transition); - } else { - View clockView = clockContainerView.getChildAt(0); - - transition.excludeTarget(clockView, /* exclude= */ true); - - TransitionSet set = new TransitionSet(); - set.addTransition(transition); - - SplitShadeTransitionAdapter adapter = - new SplitShadeTransitionAdapter(mKeyguardStatusViewController); - - // Use linear here, so the actual clock can pick its own interpolator. - adapter.setInterpolator(Interpolators.LINEAR); - adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION); - adapter.addTarget(clockView); - set.addTransition(adapter); - set.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition(mNotificationContainerParent, set); - } - } else { - transition.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition( - mNotificationContainerParent, transition); - } - } - - constraintSet.applyTo(mNotificationContainerParent); - } - mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered)); + mKeyguardStatusViewController.updateAlignment( + mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate); + mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered)); } private boolean shouldKeyguardStatusViewBeCentered() { @@ -3335,7 +3240,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation); ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection); ipw.print("mMinFraction="); ipw.println(mMinFraction); - ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered); ipw.print("mSplitShadeFullTransitionDistance="); ipw.println(mSplitShadeFullTransitionDistance); ipw.print("mSplitShadeScrimTransitionDistance="); @@ -3429,7 +3333,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mGestureRecorder = recorder; mHideExpandedRunnable = hideExpandedRunnable; - mNotificationStackScrollLayoutController.setShelfController(notificationShelfController); + if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + mNotificationStackScrollLayoutController.setShelfController( + notificationShelfController); + } mNotificationShelfController = notificationShelfController; mLockscreenShadeTransitionController.bindController(notificationShelfController); updateMaxDisplayedNotifications(true); @@ -4936,6 +4843,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } handled |= handleTouch(event); + mShadeLog.logOnTouchEventLastReturn(event, !mDozing, handled); return !mDozing || handled; } @@ -5118,6 +5026,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } break; } + mShadeLog.logHandleTouchLastReturn(event, !mGestureWaitForTouchSlop, mTracking); return !mGestureWaitForTouchSlop || mTracking; } @@ -5128,65 +5037,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } - static class SplitShadeTransitionAdapter extends Transition { - private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds"; - private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS }; - - private final KeyguardStatusViewController mController; - - SplitShadeTransitionAdapter(KeyguardStatusViewController controller) { - mController = controller; - } - - private void captureValues(TransitionValues transitionValues) { - Rect boundsRect = new Rect(); - boundsRect.left = transitionValues.view.getLeft(); - boundsRect.top = transitionValues.view.getTop(); - boundsRect.right = transitionValues.view.getRight(); - boundsRect.bottom = transitionValues.view.getBottom(); - transitionValues.values.put(PROP_BOUNDS, boundsRect); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - @Nullable - @Override - public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues, - @Nullable TransitionValues endValues) { - if (startValues == null || endValues == null) { - return null; - } - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - - Rect from = (Rect) startValues.values.get(PROP_BOUNDS); - Rect to = (Rect) endValues.values.get(PROP_BOUNDS); - - anim.addUpdateListener(animation -> { - ClockController clock = mController.getClockController(); - if (clock == null) { - return; - } - - clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction()); - }); - - return anim; - } - - @Override - public String[] getTransitionProperties() { - return TRANSITION_PROPERTIES; - } - } - private final class HeadsUpNotificationViewControllerImpl implements HeadsUpTouchHelper.HeadsUpNotificationViewController { @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index fed9b8469c4b..7812f07fc59c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -16,9 +16,9 @@ package com.android.systemui.shade +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer import com.android.systemui.shade.NotificationShadeWindowState.Buffer import com.android.systemui.statusbar.StatusBarState diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index b31ec3319781..7cb1cbe77539 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -17,6 +17,8 @@ package com.android.systemui.shade; +import static android.view.WindowInsets.Type.ime; + import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS; @@ -463,9 +465,17 @@ public class QuickSettingsController { return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight; } + private boolean isRemoteInputActiveWithKeyboardUp() { + //TODO(b/227115380) remove the isVisible(ime()) check once isRemoteInputActive is fixed. + // The check for keyboard visibility is a temporary workaround that allows QS to expand + // even when isRemoteInputActive is mistakenly returning true. + return mRemoteInputManager.isRemoteInputActive() + && mPanelView.getRootWindowInsets().isVisible(ime()); + } + public boolean isExpansionEnabled() { return mExpansionEnabledPolicy && mExpansionEnabledAmbient - && !mRemoteInputManager.isRemoteInputActive(); + && !isRemoteInputActiveWithKeyboardUp(); } public float getTransitioningToFullShadeProgress() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index da4944c20f6e..a93183865a3f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -316,4 +316,80 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { { "QSC NotificationsClippingTopBound set to $int1 - $int2" } ) } + + fun logOnTouchEventLastReturn( + event: MotionEvent, + dozing: Boolean, + handled: Boolean, + ) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = dozing + bool2 = handled + long1 = event.eventTime + long2 = event.downTime + int1 = event.action + int2 = event.classification + double1 = event.y.toDouble() + }, + { + "NPVC onTouchEvent last return: !mDozing: $bool1 || handled: $bool2 " + + "\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" + } + ) + } + + fun logHandleTouchLastReturn( + event: MotionEvent, + gestureWaitForTouchSlop: Boolean, + tracking: Boolean, + ) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = gestureWaitForTouchSlop + bool2 = tracking + long1 = event.eventTime + long2 = event.downTime + int1 = event.action + int2 = event.classification + double1 = event.y.toDouble() + }, + { + "NPVC handleTouch last return: !mGestureWaitForTouchSlop: $bool1 " + + "|| mTracking: $bool2 " + + "\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" + } + ) + } + + fun logUpdateNotificationPanelTouchState( + disabled: Boolean, + isGoingToSleep: Boolean, + shouldControlScreenOff: Boolean, + deviceInteractive: Boolean, + isPulsing: Boolean, + isFrpActive: Boolean, + ) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = disabled + bool2 = isGoingToSleep + bool3 = shouldControlScreenOff + bool4 = deviceInteractive + str1 = isPulsing.toString() + str2 = isFrpActive.toString() + }, + { + "CentralSurfaces updateNotificationPanelTouchState set disabled to: $bool1\n" + + "isGoingToSleep: $bool2, !shouldControlScreenOff: $bool3," + + "!mDeviceInteractive: $bool4, !isPulsing: $str1, isFrpActive: $str2" + } + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index c84894fc81ee..06f43f1eeaa5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -841,6 +841,19 @@ public final class KeyboardShortcutListSearch { BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet); behavior.setState(BottomSheetBehavior.STATE_EXPANDED); behavior.setSkipCollapsed(true); + behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + if (newState == BottomSheetBehavior.STATE_DRAGGING) { + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + // Do nothing. + } + }); mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true); Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 765c93ed209b..142689e88b51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1127,13 +1127,7 @@ public class KeyguardIndicationController { final boolean faceAuthUnavailable = biometricSourceType == FACE && msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE; - // TODO(b/141025588): refactor to reduce repetition of code/comments - // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong - // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to - // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the - // check of whether non-strong biometric is allowed - if (!mKeyguardUpdateMonitor - .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) + if (isPrimaryAuthRequired() && !faceAuthUnavailable) { return; } @@ -1234,7 +1228,7 @@ public class KeyguardIndicationController { private void onFaceAuthError(int msgId, String errString) { CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); mFaceAcquiredMessageDeferral.reset(); - if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) { + if (shouldSuppressFaceError(msgId)) { mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString); return; } @@ -1248,7 +1242,7 @@ public class KeyguardIndicationController { } private void onFingerprintAuthError(int msgId, String errString) { - if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) { + if (shouldSuppressFingerprintError(msgId)) { mKeyguardLogger.logBiometricMessage("suppressingFingerprintError", msgId, errString); @@ -1257,31 +1251,19 @@ public class KeyguardIndicationController { } } - private boolean shouldSuppressFingerprintError(int msgId, - KeyguardUpdateMonitor updateMonitor) { - // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong - // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to - // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the - // check of whether non-strong biometric is allowed - return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) - && !isLockoutError(msgId)) + private boolean shouldSuppressFingerprintError(int msgId) { + return ((isPrimaryAuthRequired() && !isLockoutError(msgId)) || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED); } - private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) { - // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong - // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to - // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the - // check of whether non-strong biometric is allowed - return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) - && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) + private boolean shouldSuppressFaceError(int msgId) { + return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) || msgId == FaceManager.FACE_ERROR_CANCELED || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS); } - @Override public void onTrustChanged(int userId) { if (!isCurrentUser(userId)) return; @@ -1355,6 +1337,16 @@ public class KeyguardIndicationController { } } + private boolean isPrimaryAuthRequired() { + // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong + // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to + // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the + // check of whether non-strong biometric is allowed since strong biometrics can still be + // used. + return !mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + true /* isStrongBiometric */); + } + protected boolean isPluggedInAndCharging() { return mPowerPluggedIn; } @@ -1431,7 +1423,7 @@ public class KeyguardIndicationController { private boolean canUnlockWithFingerprint() { return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser()); + getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed(); } private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java index cb4ae286d5c3..f7d37e6b1058 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java @@ -25,7 +25,6 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; -import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconContainer; @@ -35,7 +34,7 @@ import javax.inject.Inject; * Controller class for {@link NotificationShelf}. */ @NotificationRowScope -public class NotificationShelfController { +public class LegacyNotificationShelfControllerImpl implements NotificationShelfController { private final NotificationShelf mView; private final ActivatableNotificationViewController mActivatableNotificationViewController; private final KeyguardBypassController mKeyguardBypassController; @@ -44,7 +43,7 @@ public class NotificationShelfController { private AmbientState mAmbientState; @Inject - public NotificationShelfController( + public LegacyNotificationShelfControllerImpl( NotificationShelf notificationShelf, ActivatableNotificationViewController activatableNotificationViewController, KeyguardBypassController keyguardBypassController, @@ -79,56 +78,42 @@ public class NotificationShelfController { } } + @Override public NotificationShelf getView() { return mView; } + @Override public boolean canModifyColorOfNotifications() { return mAmbientState.isShadeExpanded() && !(mAmbientState.isOnKeyguard() && mKeyguardBypassController.getBypassEnabled()); } + @Override public NotificationIconContainer getShelfIcons() { return mView.getShelfIcons(); } - public @View.Visibility int getVisibility() { - return mView.getVisibility(); - } - - public void setCollapsedIcons(NotificationIconContainer notificationIcons) { - mView.setCollapsedIcons(notificationIcons); - } - + @Override public void bind(AmbientState ambientState, NotificationStackScrollLayoutController notificationStackScrollLayoutController) { mView.bind(ambientState, notificationStackScrollLayoutController); mAmbientState = ambientState; } - public int getHeight() { - return mView.getHeight(); - } - - public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState, - AmbientState ambientState) { - mAmbientState = ambientState; - mView.updateState(algorithmState, ambientState); - } - + @Override public int getIntrinsicHeight() { return mView.getIntrinsicHeight(); } + @Override public void setOnActivatedListener(ActivatableNotificationView.OnActivatedListener listener) { mView.setOnActivatedListener(listener); } + @Override public void setOnClickListener(View.OnClickListener onClickListener) { mView.setOnClickListener(onClickListener); } - public int getNotGoneIndex() { - return mView.getNotGoneIndex(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 4873c9dae89a..7eb63da38028 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -24,6 +24,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.util.AttributeSet; import android.util.IndentingPrintWriter; +import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; @@ -52,6 +53,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; +import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -64,8 +67,7 @@ import java.io.PrintWriter; * A notification shelf view that is placed inside the notification scroller. It manages the * overflow icons that don't fit into the regular list anymore. */ -public class NotificationShelf extends ActivatableNotificationView implements - View.OnLayoutChangeListener, StateListener { +public class NotificationShelf extends ActivatableNotificationView implements StateListener { private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag; private static final String TAG = "NotificationShelf"; @@ -78,7 +80,6 @@ public class NotificationShelf extends ActivatableNotificationView implements private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll"); private NotificationIconContainer mShelfIcons; - private int[] mTmp = new int[2]; private boolean mHideBackground; private int mStatusBarHeight; private boolean mEnableNotificationClipping; @@ -87,7 +88,6 @@ public class NotificationShelf extends ActivatableNotificationView implements private int mPaddingBetweenElements; private int mNotGoneIndex; private boolean mHasItemsInStableShelf; - private NotificationIconContainer mCollapsedIcons; private int mScrollFastThreshold; private int mStatusBarState; private boolean mInteractive; @@ -99,6 +99,11 @@ public class NotificationShelf extends ActivatableNotificationView implements private NotificationShelfController mController; private float mActualWidth = -1; private boolean mSensitiveRevealAnimEndabled; + private boolean mShelfRefactorFlagEnabled; + private boolean mCanModifyColorOfNotifications; + private boolean mCanInteract; + private NotificationStackScrollLayout mHostLayout; + private NotificationRoundnessManager mRoundnessManager; public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); @@ -135,6 +140,7 @@ public class NotificationShelf extends ActivatableNotificationView implements public void bind(AmbientState ambientState, NotificationStackScrollLayoutController hostLayoutController) { + assertRefactorFlagDisabled(); mAmbientState = ambientState; mHostLayoutController = hostLayoutController; hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> { @@ -142,6 +148,14 @@ public class NotificationShelf extends ActivatableNotificationView implements }); } + public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout, + NotificationRoundnessManager roundnessManager) { + if (!checkRefactorFlagEnabled()) return; + mAmbientState = ambientState; + mHostLayout = hostLayout; + mRoundnessManager = roundnessManager; + } + private void updateResources() { Resources res = getResources(); mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); @@ -233,7 +247,7 @@ public class NotificationShelf extends ActivatableNotificationView implements } else { viewState.setAlpha(1f - ambientState.getHideAmount()); } - viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0; + viewState.belowSpeedBump = getSpeedBumpIndex() == 0; viewState.hideSensitive = false; viewState.setXTranslation(getTranslationX()); viewState.hasItemsInStableShelf = lastViewState.inShelf; @@ -276,6 +290,14 @@ public class NotificationShelf extends ActivatableNotificationView implements } } + private int getSpeedBumpIndex() { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.getSpeedBumpIndex(); + } else { + return mHostLayoutController.getSpeedBumpIndex(); + } + } + /** * @param fractionToShade Fraction of lockscreen to shade transition * @param shortestWidth Shortest width to use for lockscreen shelf @@ -388,8 +410,8 @@ public class NotificationShelf extends ActivatableNotificationView implements int baseZHeight = mAmbientState.getBaseZHeight(); int clipTopAmount = 0; - for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { - ExpandableView child = mHostLayoutController.getChildAt(i); + for (int i = 0; i < getHostLayoutChildCount(); i++) { + ExpandableView child = getHostLayoutChildAt(i); if (!child.needsClippingToShelf() || child.getVisibility() == GONE) { continue; } @@ -428,7 +450,7 @@ public class NotificationShelf extends ActivatableNotificationView implements transitionAmount = inShelfAmount; } // We don't want to modify the color if the notification is hun'd - if (isLastChild && mController.canModifyColorOfNotifications()) { + if (isLastChild && canModifyColorOfNotifications()) { if (colorOfViewBeforeLast == NO_COLOR) { colorOfViewBeforeLast = ownColorUntinted; } @@ -474,11 +496,11 @@ public class NotificationShelf extends ActivatableNotificationView implements // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa. setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); - mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); + mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex()); mShelfIcons.calculateIconXTranslations(); mShelfIcons.applyIconStates(); - for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { - View child = mHostLayoutController.getChildAt(i); + for (int i = 0; i < getHostLayoutChildCount(); i++) { + View child = getHostLayoutChildAt(i); if (!(child instanceof ExpandableNotificationRow) || child.getVisibility() == GONE) { continue; @@ -493,6 +515,30 @@ public class NotificationShelf extends ActivatableNotificationView implements } } + private ExpandableView getHostLayoutChildAt(int index) { + if (mShelfRefactorFlagEnabled) { + return (ExpandableView) mHostLayout.getChildAt(index); + } else { + return mHostLayoutController.getChildAt(index); + } + } + + private int getHostLayoutChildCount() { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.getChildCount(); + } else { + return mHostLayoutController.getChildCount(); + } + } + + private boolean canModifyColorOfNotifications() { + if (mShelfRefactorFlagEnabled) { + return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded(); + } else { + return mController.canModifyColorOfNotifications(); + } + } + private void updateCornerRoundnessOnScroll( ActivatableNotificationView anv, float viewStart, @@ -507,7 +553,7 @@ public class NotificationShelf extends ActivatableNotificationView implements && anv == mAmbientState.getTrackedHeadsUpRow(); final boolean shouldUpdateCornerRoundness = viewStart < shelfStart - && !mHostLayoutController.isViewAffectedBySwipe(anv) + && !isViewAffectedBySwipe(anv) && !isUnlockedHeadsUp && !isHunGoingToShade && !anv.isAboveShelf() @@ -559,6 +605,14 @@ public class NotificationShelf extends ActivatableNotificationView implements anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false); } + private boolean isViewAffectedBySwipe(ExpandableView expandableView) { + if (!mShelfRefactorFlagEnabled) { + return mHostLayoutController.isViewAffectedBySwipe(expandableView); + } else { + return mRoundnessManager.isViewAffectedBySwipe(expandableView); + } + } + /** * Clips transient views to the top of the shelf - Transient views are only used for * disappearing views/animations and need to be clipped correctly by the shelf to ensure they @@ -566,8 +620,8 @@ public class NotificationShelf extends ActivatableNotificationView implements * swipes quickly. */ private void clipTransientViews() { - for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) { - View transientView = mHostLayoutController.getTransientView(i); + for (int i = 0; i < getHostLayoutTransientViewCount(); i++) { + View transientView = getHostLayoutTransientView(i); if (transientView instanceof ExpandableView) { ExpandableView transientExpandableView = (ExpandableView) transientView; updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1); @@ -575,6 +629,22 @@ public class NotificationShelf extends ActivatableNotificationView implements } } + private View getHostLayoutTransientView(int index) { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.getTransientView(index); + } else { + return mHostLayoutController.getTransientView(index); + } + } + + private int getHostLayoutTransientViewCount() { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.getTransientViewCount(); + } else { + return mHostLayoutController.getTransientViewCount(); + } + } + private void updateIconClipAmount(ExpandableNotificationRow row) { float maxTop = row.getTranslationY(); if (getClipTopAmount() != 0) { @@ -868,10 +938,6 @@ public class NotificationShelf extends ActivatableNotificationView implements return mShelfIcons.getIconState(icon); } - private float getFullyClosedTranslation() { - return -(getIntrinsicHeight() - mStatusBarHeight) / 2; - } - @Override public boolean hasNoContentHeight() { return true; @@ -893,7 +959,6 @@ public class NotificationShelf extends ActivatableNotificationView implements @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - updateRelativeOffset(); // we always want to clip to our sides, such that nothing can draw outside of these bounds int height = getResources().getDisplayMetrics().heightPixels; @@ -903,13 +968,6 @@ public class NotificationShelf extends ActivatableNotificationView implements } } - private void updateRelativeOffset() { - if (mCollapsedIcons != null) { - mCollapsedIcons.getLocationOnScreen(mTmp); - } - getLocationOnScreen(mTmp); - } - /** * @return the index of the notification at which the shelf visually resides */ @@ -924,33 +982,29 @@ public class NotificationShelf extends ActivatableNotificationView implements } } - /** - * @return whether the shelf has any icons in it when a potential animation has finished, i.e - * if the current state would be applied right now - */ - public boolean hasItemsInStableShelf() { - return mHasItemsInStableShelf; - } - - public void setCollapsedIcons(NotificationIconContainer collapsedIcons) { - mCollapsedIcons = collapsedIcons; - mCollapsedIcons.addOnLayoutChangeListener(this); - } - @Override public void onStateChanged(int newState) { + assertRefactorFlagDisabled(); mStatusBarState = newState; updateInteractiveness(); } private void updateInteractiveness() { - mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf; + mInteractive = canInteract() && mHasItemsInStableShelf; setClickable(mInteractive); setFocusable(mInteractive); setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } + private boolean canInteract() { + if (mShelfRefactorFlagEnabled) { + return mCanInteract; + } else { + return mStatusBarState == StatusBarState.KEYGUARD; + } + } + @Override protected boolean isInteractive() { return mInteractive; @@ -983,22 +1037,50 @@ public class NotificationShelf extends ActivatableNotificationView implements } @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, int oldBottom) { - updateRelativeOffset(); - } - - @Override public boolean needsClippingToShelf() { return false; } + private void assertRefactorFlagDisabled() { + if (mShelfRefactorFlagEnabled) { + NotificationShelfController.throwIllegalFlagStateError(false); + } + } + + private boolean checkRefactorFlagEnabled() { + if (!mShelfRefactorFlagEnabled) { + Log.wtf(TAG, + "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled."); + } + return mShelfRefactorFlagEnabled; + } + public void setController(NotificationShelfController notificationShelfController) { + assertRefactorFlagDisabled(); mController = notificationShelfController; } + public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) { + if (!checkRefactorFlagEnabled()) return; + mCanModifyColorOfNotifications = canModifyColorOfNotifications; + } + + public void setCanInteract(boolean canInteract) { + if (!checkRefactorFlagEnabled()) return; + mCanInteract = canInteract; + updateInteractiveness(); + } + public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) { - mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf); + mIndexOfFirstViewInShelf = getIndexOfViewInHostLayout(firstViewInShelf); + } + + private int getIndexOfViewInHostLayout(ExpandableView child) { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.indexOfChild(child); + } else { + return mHostLayoutController.indexOfChild(child); + } } /** @@ -1009,6 +1091,15 @@ public class NotificationShelf extends ActivatableNotificationView implements mSensitiveRevealAnimEndabled = enabled; } + public void setRefactorFlagEnabled(boolean enabled) { + mShelfRefactorFlagEnabled = enabled; + } + + public void requestRoundnessResetFor(ExpandableView child) { + if (!checkRefactorFlagEnabled()) return; + child.requestRoundnessReset(SHELF_SCROLL); + } + /** * This method resets the OnScroll roundness of a view to 0f * <p> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt new file mode 100644 index 000000000000..07cfd0d8019c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt @@ -0,0 +1,82 @@ +/* + * 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 + +import android.util.Log +import android.view.View +import android.view.View.OnClickListener +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener +import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.NotificationIconContainer + +/** Controller interface for [NotificationShelf]. */ +interface NotificationShelfController { + /** The [NotificationShelf] controlled by this Controller. */ + val view: NotificationShelf + + /** @see ExpandableView.getIntrinsicHeight */ + val intrinsicHeight: Int + + /** Container view for icons displayed in the shelf. */ + val shelfIcons: NotificationIconContainer + + /** Whether or not the shelf can modify the color of notifications in the shade. */ + fun canModifyColorOfNotifications(): Boolean + + /** @see ActivatableNotificationView.setOnActivatedListener */ + fun setOnActivatedListener(listener: OnActivatedListener) + + /** Binds the shelf to the host [NotificationStackScrollLayout], via its Controller. */ + fun bind( + ambientState: AmbientState, + notificationStackScrollLayoutController: NotificationStackScrollLayoutController, + ) + + /** @see View.setOnClickListener */ + fun setOnClickListener(listener: OnClickListener) + + companion object { + @JvmStatic + fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) { + if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + throwIllegalFlagStateError(expected = false) + } + } + + @JvmStatic + fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean = + featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled -> + if (!enabled) { + Log.wtf("NotifShelf", getErrorMessage(expected = true)) + } + } + + @JvmStatic + fun throwIllegalFlagStateError(expected: Boolean): Nothing = + error(getErrorMessage(expected)) + + private fun getErrorMessage(expected: Boolean): String = + "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " + + if (expected) "disabled" else "enabled" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java index ecd0c41ff82d..fcff4376811e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java @@ -48,6 +48,10 @@ public abstract class AnimatableProperty { View.SCALE_Y, R.id.scale_y_animator_tag, R.id.scale_y_animator_start_value_tag, R.id.scale_y_animator_end_value_tag); + public static final AnimatableProperty ALPHA = AnimatableProperty.from( + View.ALPHA, R.id.alpha_animator_tag, R.id.alpha_animator_start_value_tag, + R.id.alpha_animator_end_value_tag); + /** * Similar to X, however this doesn't allow for any other modifications other than from this * property. When using X, it's possible that the view is laid out during the animation, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index 15ad312b413e..1631ae28bf5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.PeopleHeader import com.android.systemui.statusbar.notification.icon.ConversationIconManager @@ -40,27 +41,28 @@ import javax.inject.Inject */ @CoordinatorScope class ConversationCoordinator @Inject constructor( - private val peopleNotificationIdentifier: PeopleNotificationIdentifier, - private val conversationIconManager: ConversationIconManager, - @PeopleHeader peopleHeaderController: NodeController + private val peopleNotificationIdentifier: PeopleNotificationIdentifier, + private val conversationIconManager: ConversationIconManager, + private val highPriorityProvider: HighPriorityProvider, + @PeopleHeader private val peopleHeaderController: NodeController, ) : Coordinator { private val promotedEntriesToSummaryOfSameChannel = - mutableMapOf<NotificationEntry, NotificationEntry>() + mutableMapOf<NotificationEntry, NotificationEntry>() private val onBeforeRenderListListener = OnBeforeRenderListListener { _ -> val unimportantSummaries = promotedEntriesToSummaryOfSameChannel - .mapNotNull { (promoted, summary) -> - val originalGroup = summary.parent - when { - originalGroup == null -> null - originalGroup == promoted.parent -> null - originalGroup.parent == null -> null - originalGroup.summary != summary -> null - originalGroup.children.any { it.channel == summary.channel } -> null - else -> summary.key + .mapNotNull { (promoted, summary) -> + val originalGroup = summary.parent + when { + originalGroup == null -> null + originalGroup == promoted.parent -> null + originalGroup.parent == null -> null + originalGroup.summary != summary -> null + originalGroup.children.any { it.channel == summary.channel } -> null + else -> summary.key + } } - } conversationIconManager.setUnimportantConversations(unimportantSummaries) promotedEntriesToSummaryOfSameChannel.clear() } @@ -78,21 +80,23 @@ class ConversationCoordinator @Inject constructor( } } - val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) { + val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) { override fun isInSection(entry: ListEntry): Boolean = - isConversation(entry) + highPriorityProvider.isHighPriorityConversation(entry) - override fun getComparator() = object : NotifComparator("People") { - override fun compare(entry1: ListEntry, entry2: ListEntry): Int { - val type1 = getPeopleType(entry1) - val type2 = getPeopleType(entry2) - return type2.compareTo(type1) - } - } + override fun getComparator(): NotifComparator = notifComparator - override fun getHeaderNodeController() = - // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController - if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null + override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController + } + + val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) { + // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting. + // All remaining conversations must be silent. + override fun isInSection(entry: ListEntry): Boolean = isConversation(entry) + + override fun getComparator(): NotifComparator = notifComparator + + override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController } override fun attach(pipeline: NotifPipeline) { @@ -101,15 +105,27 @@ class ConversationCoordinator @Inject constructor( } private fun isConversation(entry: ListEntry): Boolean = - getPeopleType(entry) != TYPE_NON_PERSON + getPeopleType(entry) != TYPE_NON_PERSON @PeopleNotificationType private fun getPeopleType(entry: ListEntry): Int = - entry.representativeEntry?.let { - peopleNotificationIdentifier.getPeopleNotificationType(it) - } ?: TYPE_NON_PERSON + entry.representativeEntry?.let { + peopleNotificationIdentifier.getPeopleNotificationType(it) + } ?: TYPE_NON_PERSON + + private val notifComparator: NotifComparator = object : NotifComparator("People") { + override fun compare(entry1: ListEntry, entry2: ListEntry): Int { + val type1 = getPeopleType(entry1) + val type2 = getPeopleType(entry2) + return type2.compareTo(type1) + } + } + + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController + private val conversationHeaderNodeController: NodeController? = + if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null - companion object { + private companion object { private const val TAG = "ConversationCoordinator" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 6bb5b9218ed7..02ce0d46ead8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -21,6 +21,7 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider import javax.inject.Inject /** @@ -32,6 +33,7 @@ interface NotifCoordinators : Coordinator, PipelineDumpable @CoordinatorScope class NotifCoordinatorsImpl @Inject constructor( notifPipelineFlags: NotifPipelineFlags, + sectionStyleProvider: SectionStyleProvider, dataStoreCoordinator: DataStoreCoordinator, hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, @@ -56,7 +58,7 @@ class NotifCoordinatorsImpl @Inject constructor( viewConfigCoordinator: ViewConfigCoordinator, visualStabilityCoordinator: VisualStabilityCoordinator, sensitiveContentCoordinator: SensitiveContentCoordinator, - dismissibilityCoordinator: DismissibilityCoordinator + dismissibilityCoordinator: DismissibilityCoordinator, ) : NotifCoordinators { private val mCoordinators: MutableList<Coordinator> = ArrayList() @@ -99,13 +101,20 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(dismissibilityCoordinator) // Manually add Ordered Sections - // HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default - mOrderedSections.add(headsUpCoordinator.sectioner) + mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService - mOrderedSections.add(conversationCoordinator.sectioner) // People + mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting + mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized + + sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner)) + sectionStyleProvider.setSilentSections(listOf( + conversationCoordinator.peopleSilentSectioner, + rankingCoordinator.silentSectioner, + rankingCoordinator.minimizedSectioner, + )) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index ea5cb308a2d0..1d37dcf13037 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -27,15 +27,12 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.AlertingHeader; import com.android.systemui.statusbar.notification.dagger.SilentHeader; import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import javax.inject.Inject; @@ -52,7 +49,6 @@ public class RankingCoordinator implements Coordinator { public static final boolean SHOW_ALL_SECTIONS = false; private final StatusBarStateController mStatusBarStateController; private final HighPriorityProvider mHighPriorityProvider; - private final SectionStyleProvider mSectionStyleProvider; private final NodeController mSilentNodeController; private final SectionHeaderController mSilentHeaderController; private final NodeController mAlertingHeaderController; @@ -63,13 +59,11 @@ public class RankingCoordinator implements Coordinator { public RankingCoordinator( StatusBarStateController statusBarStateController, HighPriorityProvider highPriorityProvider, - SectionStyleProvider sectionStyleProvider, @AlertingHeader NodeController alertingHeaderController, @SilentHeader SectionHeaderController silentHeaderController, @SilentHeader NodeController silentNodeController) { mStatusBarStateController = statusBarStateController; mHighPriorityProvider = highPriorityProvider; - mSectionStyleProvider = sectionStyleProvider; mAlertingHeaderController = alertingHeaderController; mSilentNodeController = silentNodeController; mSilentHeaderController = silentHeaderController; @@ -78,9 +72,6 @@ public class RankingCoordinator implements Coordinator { @Override public void attach(NotifPipeline pipeline) { mStatusBarStateController.addCallback(mStatusBarStateCallback); - mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner)); - mSectionStyleProvider.setSilentSections( - Arrays.asList(mSilentNotifSectioner, mMinimizedNotifSectioner)); pipeline.addPreGroupFilter(mSuspendedFilter); pipeline.addPreGroupFilter(mDndVisualEffectsFilter); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java index e7ef2ec084b7..731ec80817ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -16,10 +16,13 @@ package com.android.systemui.statusbar.notification.collection.provider; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationManager; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -63,7 +66,7 @@ public class HighPriorityProvider { * A GroupEntry is considered high priority if its representativeEntry (summary) or children are * high priority */ - public boolean isHighPriority(ListEntry entry) { + public boolean isHighPriority(@Nullable ListEntry entry) { if (entry == null) { return false; } @@ -78,6 +81,36 @@ public class HighPriorityProvider { || hasHighPriorityChild(entry); } + /** + * @return true if the ListEntry is high priority conversation, else false + */ + public boolean isHighPriorityConversation(@NonNull ListEntry entry) { + final NotificationEntry notifEntry = entry.getRepresentativeEntry(); + if (notifEntry == null) { + return false; + } + + if (!isPeopleNotification(notifEntry)) { + return false; + } + + if (notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT) { + return true; + } + + return isNotificationEntryWithAtLeastOneImportantChild(entry); + } + + private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull ListEntry entry) { + if (!(entry instanceof GroupEntry)) { + return false; + } + final GroupEntry groupEntry = (GroupEntry) entry; + return groupEntry.getChildren().stream().anyMatch( + childEntry -> + childEntry.getRanking().getImportance() + >= NotificationManager.IMPORTANCE_DEFAULT); + } private boolean hasHighPriorityChild(ListEntry entry) { if (entry instanceof NotificationEntry @@ -93,7 +126,6 @@ public class HighPriorityProvider { } } } - return false; } 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 a9d125508397..950ab5d2f5ef 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 @@ -299,6 +299,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ private boolean mShowGroupBackgroundWhenExpanded; + /** + * True if we always show the collapsed layout on lockscreen because vertical space is low. + */ + private boolean mSaveSpaceOnLockscreen; + + /** + * True if we use intrinsic height regardless of vertical space available on lockscreen. + */ + private boolean mIgnoreLockscreenConstraints; + private OnClickListener mExpandClickListener = new OnClickListener() { @Override public void onClick(View v) { @@ -394,6 +404,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mGroupExpansionChanging; } + public void setSaveSpaceOnLockscreen(boolean saveSpace) { + mSaveSpaceOnLockscreen = saveSpace; + } + + public boolean getSaveSpaceOnLockscreen() { + return mSaveSpaceOnLockscreen; + } + public void setGroupExpansionChanging(boolean changing) { mGroupExpansionChanging = changing; } @@ -419,15 +437,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public void setAnimationRunning(boolean running) { // Sets animations running in the private/public layouts. - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) { - for (NotificationContentView l : mLayouts) { - if (l != null) { - l.setContentAnimationRunning(running); - setIconAnimationRunning(running, l); - } - } - } else { - for (NotificationContentView l : mLayouts) { + for (NotificationContentView l : mLayouts) { + if (l != null) { + l.setContentAnimationRunning(running); setIconAnimationRunning(running, l); } } @@ -2553,11 +2565,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + public int getHeightWithoutLockscreenConstraints() { + mIgnoreLockscreenConstraints = true; + final int height = getIntrinsicHeight(); + mIgnoreLockscreenConstraints = false; + return height; + } + + @Override public int getIntrinsicHeight() { if (isUserLocked()) { return getActualHeight(); - } - if (mGuts != null && mGuts.isExposed()) { + } else if (mGuts != null && mGuts.isExposed()) { return mGuts.getIntrinsicHeight(); } else if ((isChildInGroup() && !isGroupExpanded())) { return mPrivateLayout.getMinHeight(); @@ -2579,13 +2598,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getCollapsedHeight(); } } - /** * @return {@code true} if the notification can show it's heads up layout. This is mostly true * except for legacy use cases. */ public boolean canShowHeadsUp() { - if (mOnKeyguard && !isDozing() && !isBypassEnabled() && !mEntry.isStickyAndNotDemoted()) { + if (mOnKeyguard && !isDozing() && !isBypassEnabled() && + (!mEntry.isStickyAndNotDemoted() + || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) { return false; } return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 9df6ba9910cc..5edff5f4e5d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -291,6 +291,11 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro long duration) { } + public int getHeightWithoutLockscreenConstraints() { + // ExpandableNotificationRow overrides this. + return getHeight(); + } + /** * @return The desired notification height. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java index af8d6ec727d1..98cd84dde199 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.row.dagger; +import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl; import com.android.systemui.statusbar.NotificationShelf; -import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import dagger.Binds; @@ -46,7 +46,8 @@ public interface NotificationShelfComponent { * Creates a NotificationShelfController. */ @NotificationRowScope - NotificationShelfController getNotificationShelfController(); + LegacyNotificationShelfControllerImpl getNotificationShelfController(); + /** * Dagger Module that extracts interesting properties from a NotificationShelf. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt new file mode 100644 index 000000000000..8ba65f7a1418 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt @@ -0,0 +1,48 @@ +/* + * 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.shelf.domain.interactor + +import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +/** Interactor for the [NotificationShelf] */ +@CentralSurfacesComponent.CentralSurfacesScope +class NotificationShelfInteractor +@Inject +constructor( + private val keyguardRepository: KeyguardRepository, + private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository, +) { + /** Is the shelf showing on the keyguard? */ + val isShowingOnKeyguard: Flow<Boolean> + get() = keyguardRepository.isKeyguardShowing + + /** Is the system in a state where the shelf is just a static display of notification icons? */ + val isShelfStatic: Flow<Boolean> + get() = + combine( + keyguardRepository.isKeyguardShowing, + deviceEntryFaceAuthRepository.isBypassEnabled, + ) { isKeyguardShowing, isBypassEnabled -> + isKeyguardShowing && isBypassEnabled + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt new file mode 100644 index 000000000000..b190cf658fa8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -0,0 +1,135 @@ +/* + * 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.shelf.ui.viewbinder + +import android.view.View +import android.view.accessibility.AccessibilityManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl +import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.NotificationShelfController +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView +import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController +import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController +import com.android.systemui.statusbar.notification.row.ExpandableViewController +import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.NotificationIconContainer +import com.android.systemui.statusbar.phone.NotificationTapHelper +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope +import com.android.systemui.util.kotlin.getValue +import dagger.Lazy +import javax.inject.Inject +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +/** + * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper + * around a [NotificationShelfViewBinder], so that external code can continue to depend on the + * [NotificationShelfController] interface. Once the [LegacyNotificationShelfControllerImpl] is + * removed, this class can go away and the ViewBinder can be used directly. + */ +@CentralSurfacesScope +class NotificationShelfViewBinderWrapperControllerImpl +@Inject +constructor( + private val shelf: NotificationShelf, + private val viewModel: NotificationShelfViewModel, + featureFlags: FeatureFlags, + private val notifTapHelperFactory: NotificationTapHelper.Factory, + private val a11yManager: AccessibilityManager, + private val falsingManager: FalsingManager, + private val falsingCollector: FalsingCollector, + hostControllerLazy: Lazy<NotificationStackScrollLayoutController>, +) : NotificationShelfController { + + private val hostController: NotificationStackScrollLayoutController by hostControllerLazy + + override val view: NotificationShelf + get() = unsupported + + init { + shelf.apply { + setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) + useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)) + setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)) + } + } + + fun init() { + NotificationShelfViewBinder.bind(viewModel, shelf) + + ActivatableNotificationViewController( + shelf, + notifTapHelperFactory, + ExpandableOutlineViewController(shelf, ExpandableViewController(shelf)), + a11yManager, + falsingManager, + falsingCollector, + ) + .init() + hostController.setShelf(shelf) + hostController.setOnNotificationRemovedListener { child, _ -> + view.requestRoundnessResetFor(child) + } + } + + override val intrinsicHeight: Int + get() = shelf.intrinsicHeight + + override val shelfIcons: NotificationIconContainer + get() = shelf.shelfIcons + + override fun canModifyColorOfNotifications(): Boolean = unsupported + + override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) { + shelf.setOnActivatedListener(listener) + } + + override fun bind( + ambientState: AmbientState, + notificationStackScrollLayoutController: NotificationStackScrollLayoutController, + ) = unsupported + + override fun setOnClickListener(listener: View.OnClickListener) { + shelf.setOnClickListener(listener) + } + + private val unsupported: Nothing + get() = NotificationShelfController.throwIllegalFlagStateError(expected = true) +} + +/** Binds a [NotificationShelf] to its backend. */ +object NotificationShelfViewBinder { + fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) { + shelf.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.canModifyColorOfNotifications + .onEach(shelf::setCanModifyColorOfNotifications) + .launchIn(this) + viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt new file mode 100644 index 000000000000..5e297c89dd2f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt @@ -0,0 +1,40 @@ +/* + * 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.shelf.ui.viewmodel + +import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** ViewModel for [NotificationShelf]. */ +@CentralSurfacesScope +class NotificationShelfViewModel +@Inject +constructor( + private val interactor: NotificationShelfInteractor, +) { + /** Is the shelf allowed to be clickable when it has content? */ + val isClickable: Flow<Boolean> + get() = interactor.isShowingOnKeyguard + + /** Is the shelf allowed to modify the color of notifications in the host layout? */ + val canModifyColorOfNotifications: Flow<Boolean> + get() = interactor.isShelfStatic.map { static -> !static } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java index 112d48c115c2..00b9aa42ab26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java @@ -32,6 +32,7 @@ public class AnimationProperties { public long duration; public long delay; private ArrayMap<Property, Interpolator> mInterpolatorMap; + private Consumer<Property> mAnimationCancelAction; private Consumer<Property> mAnimationEndAction; /** @@ -50,27 +51,43 @@ public class AnimationProperties { * @return a listener that will be added for a given property during its animation. */ public AnimatorListenerAdapter getAnimationFinishListener(Property property) { - if (mAnimationEndAction == null) { + if (mAnimationEndAction == null && mAnimationCancelAction == null) { return null; } + Consumer<Property> cancelAction = mAnimationCancelAction; Consumer<Property> endAction = mAnimationEndAction; return new AnimatorListenerAdapter() { private boolean mCancelled; @Override public void onAnimationCancel(Animator animation) { - mCancelled = true; + mCancelled = true; + if (cancelAction != null) { + cancelAction.accept(property); + } } @Override public void onAnimationEnd(Animator animation) { - if (!mCancelled) { + if (!mCancelled && endAction != null) { endAction.accept(property); } } }; } + /** + * Add a callback for animation cancellation. + */ + public AnimationProperties setAnimationCancelAction(Consumer<Property> listener) { + mAnimationCancelAction = listener; + return this; + } + + /** + * Add a callback for animation ending successfully. The callback will not be called when the + * animations is cancelled. + */ public AnimationProperties setAnimationEndAction(Consumer<Property> listener) { mAnimationEndAction = listener; return this; 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 e47e4146d4c0..af608a7f3a64 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 @@ -5137,8 +5137,26 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestChildrenUpdate(); } + public void setShelf(NotificationShelf shelf) { + if (!NotificationShelfController.checkRefactorFlagEnabled( + mAmbientState.getFeatureFlags())) { + return; + } + int index = -1; + if (mShelf != null) { + index = indexOfChild(mShelf); + removeView(mShelf); + } + mShelf = shelf; + addView(mShelf, index); + mAmbientState.setShelf(mShelf); + mStateAnimator.setShelf(mShelf); + shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager()); + } + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setShelfController(NotificationShelfController notificationShelfController) { + NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags()); int index = -1; if (mShelf != null) { index = indexOfChild(mShelf); 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 792746c60134..1c8727f1b092 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 @@ -73,6 +73,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; @@ -1382,6 +1383,7 @@ public class NotificationStackScrollLayoutController { } public void setShelfController(NotificationShelfController notificationShelfController) { + NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags); mView.setShelfController(notificationShelfController); } @@ -1593,6 +1595,11 @@ public class NotificationStackScrollLayoutController { mView.setOnNotificationRemovedListener(listener); } + public void setShelf(NotificationShelf shelf) { + if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return; + mView.setShelf(shelf); + } + /** * Enum for UiEvent logged from this class */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 092242814869..c7cb70c1da05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -23,12 +23,14 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.util.Compile +import com.android.systemui.util.LargeScreenUtils.shouldUseSplitNotificationShade import com.android.systemui.util.children import java.io.PrintWriter import javax.inject.Inject @@ -51,11 +53,10 @@ class NotificationStackSizeCalculator constructor( private val statusBarStateController: SysuiStatusBarStateController, private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, + private val mediaDataManager: MediaDataManager, @Main private val resources: Resources ) { - private lateinit var lastComputeHeightLog : String - /** * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf. * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space @@ -67,67 +68,157 @@ constructor( /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */ private var dividerHeight by notNull<Float>() + /** + * True when there is not enough vertical space to show at least one notification with heads up + * layout. When true, notifications always show collapsed layout. + */ + private var saveSpaceOnLockscreen = false + init { updateResources() } /** * Returns whether notifications and (shelf if visible) can fit in total space available. - * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon. + * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon. */ private fun canStackFitInSpace( stackHeight: StackHeight, - spaceForNotifications: Float, - spaceForShelf: Float, - ): Boolean { - - val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight - var canFit: Boolean + notifSpace: Float, + shelfSpace: Float, + ): FitResult { + val (notifHeight, notifHeightSaveSpace, shelfHeightWithSpaceBefore) = stackHeight if (shelfHeightWithSpaceBefore == 0f) { - canFit = notificationsHeight <= spaceForNotifications + if (notifHeight <= notifSpace) { + log { + "\tcanStackFitInSpace[FIT] = notifHeight[$notifHeight]" + + " <= notifSpace[$notifSpace]" + } + return FitResult.FIT + } + if (notifHeightSaveSpace <= notifSpace) { + log { + "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" + + " = notifHeightSaveSpace[$notifHeightSaveSpace]" + + " <= notifSpace[$notifSpace]" + } + return FitResult.FIT_IF_SAVE_SPACE + } log { - "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" + - " <= spaceForNotifications[$spaceForNotifications]" + "\tcanStackFitInSpace[NO_FIT]" + + " = notifHeightSaveSpace[$notifHeightSaveSpace] > notifSpace[$notifSpace]" } + return FitResult.NO_FIT } else { - canFit = - (notificationsHeight + shelfHeightWithSpaceBefore) <= - (spaceForNotifications + spaceForShelf) - log { - "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" + - " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + - " <= (spaceForNotifications[$spaceForNotifications] " + - " + spaceForShelf[$spaceForShelf])" + if ((notifHeight + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)) { + log { + "\tcanStackFitInSpace[FIT] = (notifHeight[$notifHeight]" + + " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + + " <= (notifSpace[$notifSpace] " + + " + spaceForShelf[$shelfSpace])" + } + return FitResult.FIT + } else if ( + (notifHeightSaveSpace + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace) + ) { + log { + "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" + + " = (notifHeightSaveSpace[$notifHeightSaveSpace]" + + " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + + " <= (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])" + } + return FitResult.FIT_IF_SAVE_SPACE + } else { + log { + "\tcanStackFitInSpace[NO_FIT]" + + " = (notifHeightSaveSpace[$notifHeightSaveSpace]" + + " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + + " > (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])" + } + return FitResult.NO_FIT } } - return canFit } /** - * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many - * notifications to show. This number is only valid in keyguard. + * Given the [notifSpace] and [shelfSpace] constraints, calculate how many notifications to + * show. This number is only valid in keyguard. * * @param totalAvailableSpace space for notifications. This includes the space for the shelf. */ fun computeMaxKeyguardNotifications( stack: NotificationStackScrollLayout, - spaceForNotifications: Float, - spaceForShelf: Float, - shelfIntrinsicHeight: Float + notifSpace: Float, + shelfSpace: Float, + shelfHeight: Float, ): Int { + log { "\n " } + log { + "computeMaxKeyguardNotifications ---" + + "\n\tnotifSpace $notifSpace" + + "\n\tspaceForShelf $shelfSpace" + + "\n\tshelfIntrinsicHeight $shelfHeight" + } + if (notifSpace + shelfSpace <= 0f) { + log { "--- No space to show anything. maxNotifs=0" } + return 0 + } log { "\n" } - val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight, - /* computeHeight= */ false) + val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight) + val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation() - var maxNotifications = + log { "\tGet maxNotifWithoutSavingSpace ---" } + val maxNotifWithoutSavingSpace = stackHeightSequence.lastIndexWhile { heightResult -> canStackFitInSpace( heightResult, - spaceForNotifications = spaceForNotifications, - spaceForShelf = spaceForShelf) + notifSpace = notifSpace, + shelfSpace = shelfSpace + ) == FitResult.FIT + } + + // How many notifications we can show at heightWithoutLockscreenConstraints + var minCountAtHeightWithoutConstraints = + if (isMediaShowing && !shouldUseSplitNotificationShade(resources)) 2 else 1 + log { + "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " + + "isMediaShowing=$isMediaShowing" + + "minCountAtHeightWithoutConstraints=$minCountAtHeightWithoutConstraints" + } + log { "\n" } + + var maxNotifications: Int + if (maxNotifWithoutSavingSpace >= minCountAtHeightWithoutConstraints) { + saveSpaceOnLockscreen = false + maxNotifications = maxNotifWithoutSavingSpace + log { + "\tDo NOT save space. maxNotifications=maxNotifWithoutSavingSpace=$maxNotifications" + } + } else { + log { "\tSAVE space ---" } + saveSpaceOnLockscreen = true + maxNotifications = + stackHeightSequence.lastIndexWhile { heightResult -> + canStackFitInSpace( + heightResult, + notifSpace = notifSpace, + shelfSpace = shelfSpace + ) != FitResult.NO_FIT + } + log { "\t--- maxNotifications=$maxNotifications" } + } + + // Must update views immediately to avoid mismatches between initial HUN layout height + // and the height adapted to lockscreen space constraints, which causes jump cuts. + stack.showableChildren().toList().forEach { currentNotification -> + run { + if (currentNotification is ExpandableNotificationRow) { + currentNotification.saveSpaceOnLockscreen = saveSpaceOnLockscreen + } } + } if (onLockscreen()) { maxNotifications = min(maxKeyguardNotifications, maxNotifications) @@ -137,53 +228,80 @@ constructor( maxNotifications = max(0, maxNotifications) log { val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else "" - "computeMaxKeyguardNotifications(" + - " spaceForNotifications=$spaceForNotifications" + - " spaceForShelf=$spaceForShelf" + - " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence" + "--- computeMaxKeyguardNotifications(" + + " notifSpace=$notifSpace" + + " shelfSpace=$shelfSpace" + + " shelfHeight=$shelfHeight) -> $maxNotifications$sequence" } + log { "\n" } return maxNotifications } /** - * Given the [maxNotifications] constraint, calculates the height of the + * Given the [maxNotifs] constraint, calculates the height of the * [NotificationStackScrollLayout]. This might or might not be in keyguard. * * @param stack stack containing notifications as children. - * @param maxNotifications Maximum number of notifications. When reached, the others will go - * into the shelf. - * @param shelfIntrinsicHeight height of the shelf, without any padding. It might be zero. - * + * @param maxNotifs Maximum number of notifications. When reached, the others will go into the + * shelf. + * @param shelfHeight height of the shelf, without any padding. It might be zero. * @return height of the stack, including shelf height, if needed. */ fun computeHeight( stack: NotificationStackScrollLayout, - maxNotifications: Int, - shelfIntrinsicHeight: Float + maxNotifs: Int, + shelfHeight: Float ): Float { log { "\n" } - lastComputeHeightLog = "" - val heightPerMaxNotifications = - computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight, - /* computeHeight= */ true) - - val (notificationsHeight, shelfHeightWithSpaceBefore) = - heightPerMaxNotifications.elementAtOrElse(maxNotifications) { - heightPerMaxNotifications.last() // Height with all notifications visible. + log { "computeHeight ---" } + + val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight) + + val (notifsHeight, notifsHeightSavingSpace, shelfHeightWithSpaceBefore) = + stackHeightSequence.elementAtOrElse(maxNotifs) { + stackHeightSequence.last() // Height with all notifications visible. + } + + var height: Float + if (saveSpaceOnLockscreen) { + height = notifsHeightSavingSpace + shelfHeightWithSpaceBefore + log { + "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" + + " -> $height=($notifsHeightSavingSpace+$shelfHeightWithSpaceBefore)," + + " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen" + } + } else { + height = notifsHeight + shelfHeightWithSpaceBefore + log { + "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" + + " -> ${height}=($notifsHeight+$shelfHeightWithSpaceBefore)" + + " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen" } - lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," + - "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " + - "${notificationsHeight + shelfHeightWithSpaceBefore}" + - " = ($notificationsHeight + $shelfHeightWithSpaceBefore)" - log { - lastComputeHeightLog } - return notificationsHeight + shelfHeightWithSpaceBefore + return height } + private enum class FitResult { + FIT, + FIT_IF_SAVE_SPACE, + NO_FIT + } + + data class SpaceNeeded( + // Float height of spaceNeeded when showing heads up layout for FSI HUNs. + val whenEnoughSpace: Float, + + // Float height of space needed when showing collapsed layout for FSI HUNs. + val whenSavingSpace: Float + ) + private data class StackHeight( // Float height with ith max notifications (not including shelf) - val notificationsHeight: Float, + val notifsHeight: Float, + + // Float height with ith max notifications + // (not including shelf, using collapsed layout for FSI HUN) + val notifsHeightSavingSpace: Float, // Float height of shelf (0 if shelf is not showing), and space before the shelf that // changes during the lockscreen <=> full shade transition. @@ -193,20 +311,27 @@ constructor( private fun computeHeightPerNotificationLimit( stack: NotificationStackScrollLayout, shelfHeight: Float, - computeHeight: Boolean ): Sequence<StackHeight> = sequence { - log { "computeHeightPerNotificationLimit" } - val children = stack.showableChildren().toList() var notifications = 0f + var notifsWithCollapsedHun = 0f var previous: ExpandableView? = null val onLockscreen = onLockscreen() // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState). - yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight)) + yield( + StackHeight( + notifsHeight = 0f, + notifsHeightSavingSpace = 0f, + shelfHeightWithSpaceBefore = shelfHeight + ) + ) children.forEachIndexed { i, currentNotification -> - notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen) + val space = getSpaceNeeded(currentNotification, i, previous, stack, onLockscreen) + notifications += space.whenEnoughSpace + notifsWithCollapsedHun += space.whenSavingSpace + previous = currentNotification val shelfWithSpaceBefore = @@ -219,22 +344,23 @@ constructor( stack, previous = currentNotification, current = children[firstViewInShelfIndex], - currentIndex = firstViewInShelfIndex) + currentIndex = firstViewInShelfIndex + ) spaceBeforeShelf + shelfHeight } - val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " + - "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore" - if (computeHeight) { - lastComputeHeightLog += "\n" + currentLog - } log { - currentLog + "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " + + "notifsHeightSavingSpace=$notifsWithCollapsedHun" + + " shelfWithSpaceBefore=$shelfWithSpaceBefore" } yield( StackHeight( - notificationsHeight = notifications, - shelfHeightWithSpaceBefore = shelfWithSpaceBefore)) + notifsHeight = notifications, + notifsHeightSavingSpace = notifsWithCollapsedHun, + shelfHeightWithSpaceBefore = shelfWithSpaceBefore + ) + ) } } @@ -256,32 +382,46 @@ constructor( } @VisibleForTesting - fun spaceNeeded( + fun getSpaceNeeded( view: ExpandableView, visibleIndex: Int, previousView: ExpandableView?, stack: NotificationStackScrollLayout, - onLockscreen: Boolean - ): Float { + onLockscreen: Boolean, + ): SpaceNeeded { assert(view.isShowable(onLockscreen)) + // Must use heightWithoutLockscreenConstraints because intrinsicHeight references + // mSaveSpaceOnLockscreen and using intrinsicHeight here will result in stack overflow. + val height = view.heightWithoutLockscreenConstraints.toFloat() + val gapAndDividerHeight = + calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex) + var size = if (onLockscreen) { if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) { - view.intrinsicHeight.toFloat() + height } else { view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat() } } else { - view.intrinsicHeight.toFloat() + height + } + size += gapAndDividerHeight + + var sizeWhenSavingSpace = + if (onLockscreen) { + view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat() + } else { + height } + sizeWhenSavingSpace += gapAndDividerHeight - size += calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex) - return size + return SpaceNeeded(size, sizeWhenSavingSpace) } fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog") + pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen") } private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean { 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 0c8e9e56b04a..7596ce08a53c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -192,6 +192,7 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shade.ShadeLogger; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CircleReveal; @@ -505,6 +506,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /** Controller for the Shade. */ @VisibleForTesting NotificationPanelViewController mNotificationPanelViewController; + private final ShadeLogger mShadeLogger; // settings private QSPanelController mQSPanelController; @@ -738,6 +740,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { KeyguardViewMediator keyguardViewMediator, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, + ShadeLogger shadeLogger, @UiBackground Executor uiBgExecutor, NotificationMediaManager notificationMediaManager, NotificationLockscreenUserManager lockScreenUserManager, @@ -830,6 +833,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mKeyguardViewMediator = keyguardViewMediator; mDisplayMetrics = displayMetrics; mMetricsLogger = metricsLogger; + mShadeLogger = shadeLogger; mUiBgExecutor = uiBgExecutor; mMediaManager = notificationMediaManager; mLockscreenUserManager = lockScreenUserManager; @@ -3672,6 +3676,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing()) || goingToSleepWithoutAnimation || mDeviceProvisionedController.isFrpActive(); + mShadeLogger.logUpdateNotificationPanelTouchState(disabled, isGoingToSleep(), + !mDozeParameters.shouldControlScreenOff(), !mDeviceInteractive, + !mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive()); + mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled); mNotificationIconAreaController.setAnimationsEnabled(!disabled); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java deleted file mode 100644 index 076e5f1c1ce7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.WindowInsets; -import android.widget.FrameLayout; - -/** - * A view group which contains the preview of phone/camera and draws a black bar at the bottom as - * the fake navigation bar. - */ -public class KeyguardPreviewContainer extends FrameLayout { - - private Drawable mBlackBarDrawable = new Drawable() { - @Override - public void draw(Canvas canvas) { - canvas.save(); - canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight()); - canvas.drawColor(Color.BLACK); - canvas.restore(); - } - - @Override - public void setAlpha(int alpha) { - // noop - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - // noop - } - - @Override - public int getOpacity() { - return android.graphics.PixelFormat.OPAQUE; - } - }; - - public KeyguardPreviewContainer(Context context, AttributeSet attrs) { - super(context, attrs); - setBackground(mBlackBarDrawable); - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - setPadding(0, 0, 0, insets.getStableInsetBottom()); - return super.onApplyWindowInsets(insets); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index eb19c0d2ad71..057fa42bd347 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -193,7 +193,6 @@ public class NotificationIconAreaController implements public void setupShelf(NotificationShelfController notificationShelfController) { mShelfIcons = notificationShelfController.getShelfIcons(); - notificationShelfController.setCollapsedIcons(mNotificationIcons); } public void onDensityOrFontScaleChanged(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 118bfc55dd4c..0cd3401ae541 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -175,15 +175,19 @@ class UnlockedScreenOffAnimationController @Inject constructor( // We animate the Y properly separately using the PropertyAnimator, as the panel // view also needs to update the end position. PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y) - PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY, + PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY, AnimationProperties().setDuration(duration.toLong()), true /* animate */) - keyguardView.animate() + // Cancel any existing CUJs before starting the animation + interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) + + PropertyAnimator.setProperty( + keyguardView, AnimatableProperty.ALPHA, 1f, + AnimationProperties() + .setDelay(0) .setDuration(duration.toLong()) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(1f) - .withEndAction { + .setAnimationEndAction { aodUiAnimationPlaying = false // Lock the keyguard if it was waiting for the screen off animation to end. @@ -199,30 +203,23 @@ class UnlockedScreenOffAnimationController @Inject constructor( // Done going to sleep, reset this flag. decidedToAnimateGoingToSleep = null - // We need to unset the listener. These are persistent for future animators - keyguardView.animate().setListener(null) interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) } - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationCancel(animation: Animator?) { - // If we're cancelled, reset state flags/listeners. The end action above - // will not be called, which is what we want since that will finish the - // screen off animation and show the lockscreen, which we don't want if we - // were cancelled. - aodUiAnimationPlaying = false - decidedToAnimateGoingToSleep = null - keyguardView.animate().setListener(null) - - interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) - } - - override fun onAnimationStart(animation: Animator?) { - interactionJankMonitor.begin( - mCentralSurfaces.notificationShadeWindowView, - CUJ_SCREEN_OFF_SHOW_AOD) - } - }) - .start() + .setAnimationCancelAction { + // If we're cancelled, reset state flags/listeners. The end action above + // will not be called, which is what we want since that will finish the + // screen off animation and show the lockscreen, which we don't want if we + // were cancelled. + aodUiAnimationPlaying = false + decidedToAnimateGoingToSleep = null + interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) + } + .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN), + true /* animate */) + interactionJankMonitor.begin( + mCentralSurfaces.notificationShadeWindowView, + CUJ_SCREEN_OFF_SHOW_AOD + ) } override fun onStartedWakingUp() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 0929233feb88..5d4addab240a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -33,6 +33,7 @@ import com.android.systemui.biometrics.AuthRippleView; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.privacy.OngoingPrivacyChip; import com.android.systemui.settings.UserTracker; @@ -44,12 +45,14 @@ import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationsQuickSettingsContainer; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent; +import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator; @@ -76,6 +79,7 @@ import com.android.systemui.util.settings.SecureSettings; import java.util.concurrent.Executor; import javax.inject.Named; +import javax.inject.Provider; import dagger.Binds; import dagger.Module; @@ -130,16 +134,24 @@ public abstract class StatusBarViewModule { @Provides @CentralSurfacesComponent.CentralSurfacesScope public static NotificationShelfController providesStatusBarWindowView( + FeatureFlags featureFlags, + Provider<NotificationShelfViewBinderWrapperControllerImpl> newImpl, NotificationShelfComponent.Builder notificationShelfComponentBuilder, NotificationShelf notificationShelf) { - NotificationShelfComponent component = notificationShelfComponentBuilder - .notificationShelf(notificationShelf) - .build(); - NotificationShelfController notificationShelfController = - component.getNotificationShelfController(); - notificationShelfController.init(); - - return notificationShelfController; + if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + NotificationShelfViewBinderWrapperControllerImpl impl = newImpl.get(); + impl.init(); + return impl; + } else { + NotificationShelfComponent component = notificationShelfComponentBuilder + .notificationShelf(notificationShelf) + .build(); + LegacyNotificationShelfControllerImpl notificationShelfController = + component.getNotificationShelfController(); + notificationShelfController.init(); + + return notificationShelfController; + } } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt index 075e6ec11ae7..a05ab849088d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt @@ -26,15 +26,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsVi import java.io.PrintWriter 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.StateFlow import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** @@ -46,39 +38,16 @@ import kotlinx.coroutines.launch * the list of available mobile lines of service for which we want to show icons. */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") -@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileUiAdapter @Inject constructor( - interactor: MobileIconsInteractor, private val iconController: StatusBarIconController, - private val iconsViewModelFactory: MobileIconsViewModel.Factory, + val mobileIconsViewModel: MobileIconsViewModel, private val logger: MobileViewLogger, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { - private val mobileSubIds: Flow<List<Int>> = - interactor.filteredSubscriptions.mapLatest { subscriptions -> - subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } - } - - /** - * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is - * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which - * house the mobile infos. - * - * NOTE: this should go away as the view presenter learns more about this data pipeline - */ - private val mobileSubIdsState: StateFlow<List<Int>> = - mobileSubIds - .distinctUntilChanged() - .onEach { logger.logUiAdapterSubIdsUpdated(it) } - .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) - - /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */ - val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState) - private var isCollecting: Boolean = false private var lastValue: List<Int>? = null @@ -90,7 +59,7 @@ constructor( if (statusBarPipelineFlags.useNewMobileIcons()) { scope.launch { isCollecting = true - mobileSubIds.collectLatest { + mobileIconsViewModel.subscriptionIdsFlow.collectLatest { logger.logUiAdapterSubIdsSentToIconController(it) lastValue = it iconController.setNewMobileIconSubIds(it) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt index 90dff23c637c..f2f91430eba6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt @@ -41,15 +41,6 @@ constructor( private val collectionStatuses = mutableMapOf<String, Boolean>() - fun logUiAdapterSubIdsUpdated(subs: List<Int>) { - buffer.log( - TAG, - LogLevel.INFO, - { str1 = subs.toString() }, - { "Sub IDs in MobileUiAdapter updated internally: $str1" }, - ) - } - fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index dce7bf21fadd..bfd133e6830c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -37,7 +37,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn /** Common interface for all of the location-based mobile icon view models. */ @@ -80,7 +79,12 @@ constructor( ) : MobileIconViewModelCommon { /** Whether or not to show the error state of [SignalDrawable] */ private val showExclamationMark: Flow<Boolean> = - iconInteractor.isDefaultDataEnabled.mapLatest { !it } + combine( + iconInteractor.isDefaultDataEnabled, + iconInteractor.isDefaultConnectionFailed, + ) { isDefaultDataEnabled, isDefaultConnectionFailed -> + !isDefaultDataEnabled || isDefaultConnectionFailed + } override val isVisible: StateFlow<Boolean> = if (!constants.hasDataCapabilities) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 2b90065284d0..9af5e836659f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -29,18 +29,23 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** * View model for describing the system's current mobile cellular connections. The result is a list * of [MobileIconViewModel]s which describe the individual icons and can be bound to - * [ModernStatusBarMobileView] + * [ModernStatusBarMobileView]. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton class MobileIconsViewModel @Inject constructor( - val subscriptionIdsFlow: StateFlow<List<Int>>, val logger: MobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, @@ -51,6 +56,13 @@ constructor( ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() + val subscriptionIdsFlow: StateFlow<List<Int>> = + interactor.filteredSubscriptions + .mapLatest { subscriptions -> + subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + init { scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } } @@ -79,30 +91,4 @@ constructor( val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } } - - @SysUISingleton - class Factory - @Inject - constructor( - private val logger: MobileViewLogger, - private val verboseLogger: VerboseMobileViewLogger, - private val interactor: MobileIconsInteractor, - private val airplaneModeInteractor: AirplaneModeInteractor, - private val constants: ConnectivityConstants, - @Application private val scope: CoroutineScope, - private val statusBarPipelineFlags: StatusBarPipelineFlags, - ) { - fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel { - return MobileIconsViewModel( - subscriptionIdsFlow, - logger, - verboseLogger, - interactor, - airplaneModeInteractor, - constants, - scope, - statusBarPipelineFlags, - ) - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 08c14e743bb6..f800cf496ca9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -33,6 +33,15 @@ interface WifiRepository { /** Observable for the current wifi network activity. */ val wifiActivity: StateFlow<DataActivityModel> + + /** + * Returns true if the device is currently connected to a wifi network with a valid SSID and + * false otherwise. + */ + fun isWifiConnectedWithValidSsid(): Boolean { + val currentNetwork = wifiNetwork.value + return currentNetwork is WifiNetworkModel.Active && currentNetwork.hasValidSsid() + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index 96ab074c6e56..1a41abf031bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.pipeline.wifi.domain.interactor -import android.net.wifi.WifiManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel @@ -76,7 +75,7 @@ constructor( when { info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> info.passpointProviderFriendlyName - info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid + info.hasValidSsid() -> info.ssid else -> null } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index 0923d7848d8c..4b33c88cea30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.wifi.shared.model +import android.net.wifi.WifiManager.UNKNOWN_SSID import android.telephony.SubscriptionManager import androidx.annotation.VisibleForTesting import com.android.systemui.log.table.Diffable @@ -223,6 +224,11 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } } + /** Returns true if this network has a valid SSID and false otherwise. */ + fun hasValidSsid(): Boolean { + return ssid != null && ssid != UNKNOWN_SSID + } + override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { if (prevVal !is Active) { logFull(row) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 654ba04eba7a..1e63b2a4b3e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -21,6 +21,8 @@ import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; import static android.os.BatteryManager.EXTRA_HEALTH; import static android.os.BatteryManager.EXTRA_PRESENT; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS; + import android.annotation.WorkerThread; import android.content.BroadcastReceiver; import android.content.Context; @@ -169,7 +171,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Override public void setPowerSaveMode(boolean powerSave, View view) { if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view)); - BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true); + BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true, + SAVER_ENABLED_QS); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index f1269f2b012a..673819b20e4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -38,14 +38,14 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Objects; import javax.inject.Inject; -import dagger.Lazy; - /** */ @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index e9f0dcb4eb51..928e0115287d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -61,6 +61,7 @@ import javax.inject.Inject; * Manages the user switcher on the Keyguard. */ @KeyguardUserSwitcherScope +@Deprecated public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> { private static final String TAG = "KeyguardUserSwitcherController"; diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index a20a5b2fdbbc..e819f946a6d6 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -31,6 +31,7 @@ import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.widget.ImageView import android.widget.TextView +import androidx.annotation.DimenRes import androidx.annotation.IdRes import androidx.annotation.VisibleForTesting import com.android.internal.widget.CachingIconView @@ -180,8 +181,9 @@ constructor( // Button val buttonView = currentView.requireViewById<TextView>(R.id.end_button) - if (newInfo.endItem is ChipbarEndItem.Button) { - TextViewBinder.bind(buttonView, newInfo.endItem.text) + val hasButton = newInfo.endItem is ChipbarEndItem.Button + if (hasButton) { + TextViewBinder.bind(buttonView, (newInfo.endItem as ChipbarEndItem.Button).text) val onClickListener = View.OnClickListener { clickedView -> @@ -196,6 +198,12 @@ constructor( buttonView.visibility = View.GONE } + currentView + .getInnerView() + .setEndPadding( + if (hasButton) R.dimen.chipbar_outer_padding_half else R.dimen.chipbar_outer_padding + ) + // ---- Overall accessibility ---- val iconDesc = newInfo.startIcon.icon.contentDescription val loadedIconDesc = @@ -309,6 +317,15 @@ constructor( viewUtil.setRectToViewWindowLocation(view, outRect) } + private fun View.setEndPadding(@DimenRes endPaddingDimen: Int) { + this.setPaddingRelative( + this.paddingStart, + this.paddingTop, + context.resources.getDimensionPixelSize(endPaddingDimen), + this.paddingBottom, + ) + } + private fun Boolean.visibleIfTrue(): Int { return if (this) { View.VISIBLE diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index 6e58f2296c86..52f2d11f814e 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -18,7 +18,7 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.VibrationEffect import android.view.View -import androidx.annotation.ColorRes +import androidx.annotation.AttrRes import com.android.systemui.R import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon @@ -49,7 +49,9 @@ data class ChipbarInfo( override val priority: ViewPriority, ) : TemporaryViewInfo() { companion object { - @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color + // LINT.IfChange + @AttrRes val DEFAULT_ICON_TINT = com.android.internal.R.attr.materialColorOnSecondaryFixed + // LINT.ThenChange(systemui/res/layout/chipbar.xml) } } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt new file mode 100644 index 000000000000..c587f2edd601 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt @@ -0,0 +1,32 @@ +/* + * 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.util.kotlin + +import dagger.Lazy +import kotlin.reflect.KProperty + +/** + * Extension operator that allows developers to use [dagger.Lazy] as a property delegate: + * ```kotlin + * class MyClass @Inject constructor( + * lazyDependency: dagger.Lazy<Foo>, + * ) { + * val dependency: Foo by lazyDependency + * } + * ``` + */ +operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get() diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index 209ea41fed61..58cffa761a66 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -58,6 +58,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -418,6 +419,7 @@ public class GarbageMonitor implements Dumpable { @Inject public MemoryTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -428,7 +430,7 @@ public class GarbageMonitor implements Dumpable { GarbageMonitor monitor, PanelInteractor panelInteractor ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); gm = monitor; mPanelInteractor = panelInteractor; diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java index 35af44442ca9..e3ed2b405fb0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java @@ -16,20 +16,34 @@ package com.android.systemui.volume; +import static android.app.PendingIntent.FLAG_IMMUTABLE; + import android.annotation.StringRes; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; -import android.os.CountDownTimer; +import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; +import com.android.internal.messages.nano.SystemMessageProto; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.util.NotificationChannels; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; /** * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}: @@ -53,34 +67,58 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; * communication between the native audio framework that implements the dose computation and the * audio service. */ -public abstract class CsdWarningDialog extends SystemUIDialog +public class CsdWarningDialog extends SystemUIDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { private static final String TAG = Util.logTag(CsdWarningDialog.class); private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds // time after which action is taken when the user hasn't ack'd or dismissed the dialog - private static final int NO_ACTION_TIMEOUT_MS = 5000; + public static final int NO_ACTION_TIMEOUT_MS = 5000; private final Context mContext; private final AudioManager mAudioManager; private final @AudioManager.CsdWarning int mCsdWarning; private final Object mTimerLock = new Object(); + /** * Timer to keep track of how long the user has before an action (here volume reduction) is * taken on their behalf. */ @GuardedBy("mTimerLock") - private final CountDownTimer mNoUserActionTimer; + private Runnable mNoUserActionRunnable; + private Runnable mCancelScheduledNoUserActionRunnable = null; + + private final DelayableExecutor mDelayableExecutor; + private NotificationManager mNotificationManager; + private Runnable mOnCleanup; private long mShowTime; - public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context, - AudioManager audioManager) { + /** + * To inject dependencies and allow for easier testing + */ + @AssistedFactory + public interface Factory { + /** + * Create a dialog object + */ + CsdWarningDialog create(int csdWarning, Runnable onCleanup); + } + + @AssistedInject + public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context, + AudioManager audioManager, NotificationManager notificationManager, + @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) { super(context); mCsdWarning = csdWarning; mContext = context; mAudioManager = audioManager; + mNotificationManager = notificationManager; + mOnCleanup = onCleanup; + + mDelayableExecutor = delayableExecutor; + getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); setShowForAllUsers(true); setMessage(mContext.getString(getStringForWarning(csdWarning))); @@ -95,25 +133,24 @@ public abstract class CsdWarningDialog extends SystemUIDialog Context.RECEIVER_EXPORTED_UNAUDITED); if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { - mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) { - @Override - public void onTick(long millisUntilFinished) { } - - @Override - public void onFinish() { - if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { - // unlike on the 5x dose repeat, level is only reduced to RS1 - // when the warning is not acknowledged quick enough - mAudioManager.lowerVolumeToRs1(); - } + mNoUserActionRunnable = () -> { + if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { + // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning + // is not acknowledged quickly enough + mAudioManager.lowerVolumeToRs1(); + sendNotification(); } }; } else { - mNoUserActionTimer = null; + mNoUserActionRunnable = null; } } - protected abstract void cleanUp(); + private void cleanUp() { + if (mOnCleanup != null) { + mOnCleanup.run(); + } + } // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new @@ -153,12 +190,9 @@ public abstract class CsdWarningDialog extends SystemUIDialog super.onStart(); mShowTime = System.currentTimeMillis(); synchronized (mTimerLock) { - if (mNoUserActionTimer != null) { - new Thread(() -> { - synchronized (mTimerLock) { - mNoUserActionTimer.start(); - } - }).start(); + if (mNoUserActionRunnable != null) { + mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed( + mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS); } } } @@ -166,8 +200,8 @@ public abstract class CsdWarningDialog extends SystemUIDialog @Override protected void onStop() { synchronized (mTimerLock) { - if (mNoUserActionTimer != null) { - mNoUserActionTimer.cancel(); + if (mCancelScheduledNoUserActionRunnable != null) { + mCancelScheduledNoUserActionRunnable.run(); } } } @@ -212,4 +246,32 @@ public abstract class CsdWarningDialog extends SystemUIDialog Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception()); return com.android.internal.R.string.csd_dose_reached_warning; } + + + /** + * In case user did not respond to the dialog, they still need to know volume was lowered. + */ + private void sendNotification() { + Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS); + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, + FLAG_IMMUTABLE); + + String text = mContext.getString(R.string.csd_system_lowered_text); + String title = mContext.getString(R.string.csd_lowered_title); + + Notification.Builder builder = + new Notification.Builder(mContext, NotificationChannels.ALERTS) + .setSmallIcon(R.drawable.hearing) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(pendingIntent) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setAutoCancel(true) + .setCategory(Notification.CATEGORY_SYSTEM); + + mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO, + builder.build()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 95cc12a48bb2..3c007f99a654 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -263,6 +263,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final ConfigurationController mConfigurationController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final VolumePanelFactory mVolumePanelFactory; + private final CsdWarningDialog.Factory mCsdWarningDialogFactory; private final ActivityStarter mActivityStarter; private boolean mShowing; @@ -311,6 +312,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, InteractionJankMonitor interactionJankMonitor, DeviceConfigProxy deviceConfigProxy, Executor executor, + CsdWarningDialog.Factory csdWarningDialogFactory, DumpManager dumpManager) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); @@ -322,6 +324,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mConfigurationController = configurationController; mMediaOutputDialogFactory = mediaOutputDialogFactory; mVolumePanelFactory = volumePanelFactory; + mCsdWarningDialogFactory = csdWarningDialogFactory; mActivityStarter = activityStarter; mShowActiveStreamOnly = showActiveStreamOnly(); mHasSeenODICaptionsTooltip = @@ -2084,21 +2087,21 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, rescheduleTimeoutH(); } - private void showCsdWarningH(int csdWarning, int durationMs) { + @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) { synchronized (mSafetyWarningLock) { + if (mCsdDialog != null) { return; } - mCsdDialog = new CsdWarningDialog(csdWarning, - mContext, mController.getAudioManager()) { - @Override - protected void cleanUp() { - synchronized (mSafetyWarningLock) { - mCsdDialog = null; - } - recheckH(null); + + final Runnable cleanUp = () -> { + synchronized (mSafetyWarningLock) { + mCsdDialog = null; } + recheckH(null); }; + + mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp); mCsdDialog.show(); } recheckH(null); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index 0ab6c690e1e1..14d3ca334073 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -30,17 +30,18 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.volume.CsdWarningDialog; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; import com.android.systemui.volume.VolumeDialogImpl; import com.android.systemui.volume.VolumePanelFactory; -import java.util.concurrent.Executor; - import dagger.Binds; import dagger.Module; import dagger.Provides; +import java.util.concurrent.Executor; + /** Dagger Module for code in the volume package. */ @Module @@ -63,6 +64,7 @@ public interface VolumeModule { InteractionJankMonitor interactionJankMonitor, DeviceConfigProxy deviceConfigProxy, @Main Executor executor, + CsdWarningDialog.Factory csdFactory, DumpManager dumpManager) { VolumeDialogImpl impl = new VolumeDialogImpl( context, @@ -76,6 +78,7 @@ public interface VolumeModule { interactionJankMonitor, deviceConfigProxy, executor, + csdFactory, dumpManager); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java deleted file mode 100644 index e93e86291535..000000000000 --- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java +++ /dev/null @@ -1,174 +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 androidx.core.animation; - -import android.os.Looper; -import android.os.SystemClock; -import android.util.AndroidRuntimeException; - -import androidx.annotation.NonNull; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import java.util.ArrayList; -import java.util.List; - -/** - * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to - * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static - * list of callbacks. - * - * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code. - */ -public final class AnimatorTestRule2 implements TestRule { - - class TestAnimationHandler extends AnimationHandler { - TestAnimationHandler() { - super(new TestProvider()); - } - - List<AnimationFrameCallback> animationCallbacks = new ArrayList<>(); - - @Override - void addAnimationFrameCallback(AnimationFrameCallback callback) { - animationCallbacks.add(callback); - callback.doAnimationFrame(getCurrentTime()); - } - - @Override - public void removeCallback(AnimationFrameCallback callback) { - int id = animationCallbacks.indexOf(callback); - if (id >= 0) { - animationCallbacks.set(id, null); - } - } - - void onAnimationFrame(long frameTime) { - for (int i = 0; i < animationCallbacks.size(); i++) { - final AnimationFrameCallback callback = animationCallbacks.get(i); - if (callback == null) { - continue; - } - callback.doAnimationFrame(frameTime); - } - } - - @Override - void autoCancelBasedOn(ObjectAnimator objectAnimator) { - for (int i = animationCallbacks.size() - 1; i >= 0; i--) { - AnimationFrameCallback cb = animationCallbacks.get(i); - if (cb == null) { - continue; - } - if (objectAnimator.shouldAutoCancel(cb)) { - ((Animator) animationCallbacks.get(i)).cancel(); - } - } - } - } - - final TestAnimationHandler mTestHandler; - final long mStartTime; - private long mTotalTimeDelta = 0; - private final Object mLock = new Object(); - - public AnimatorTestRule2() { - mStartTime = SystemClock.uptimeMillis(); - mTestHandler = new TestAnimationHandler(); - } - - @NonNull - @Override - public Statement apply(@NonNull final Statement base, @NonNull Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - AnimationHandler.setTestHandler(mTestHandler); - try { - base.evaluate(); - } finally { - AnimationHandler.setTestHandler(null); - } - } - }; - } - - /** - * Advances the animation clock by the given amount of delta in milliseconds. This call will - * produce an animation frame to all the ongoing animations. This method needs to be - * called on the same thread as {@link Animator#start()}. - * - * @param timeDelta the amount of milliseconds to advance - */ - public void advanceTimeBy(long timeDelta) { - if (Looper.myLooper() == null) { - // Throw an exception - throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be" - + "called on Looper threads"); - } - synchronized (mLock) { - // Advance time & pulse a frame - mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta; - } - // produce a frame - mTestHandler.onAnimationFrame(getCurrentTime()); - } - - - /** - * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a - * different time than the time tracked by {@link SystemClock} This method needs to be called on - * the same thread as {@link Animator#start()}. - */ - public long getCurrentTime() { - if (Looper.myLooper() == null) { - // Throw an exception - throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be" - + "called on Looper threads"); - } - synchronized (mLock) { - return mStartTime + mTotalTimeDelta; - } - } - - - private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider { - TestProvider() { - } - - @Override - public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) { - callback.doAnimationFrame(getCurrentTime()); - } - - @Override - public void postFrameCallback() { - } - - @Override - public void setFrameDelay(long delay) { - } - - @Override - public long getFrameDelay() { - return 0; - } - } -} - diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt index bddd60b5970a..e7738aff6278 100644 --- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt +++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt @@ -30,7 +30,7 @@ import org.junit.runner.RunWith @RunWithLooper(setAsMainLooper = true) class AnimatorTestRuleTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() @Test fun testA() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index ecf7e0d46373..5557efa97e3e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -38,8 +38,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.PackageManager; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; import android.provider.Settings; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -52,6 +50,8 @@ import android.text.TextUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository; +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -88,8 +88,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0, TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "", DATA_ROAMING_ENABLE, null, null, null, null, false, null, ""); - @Mock - private WifiManager mWifiManager; + private FakeWifiRepository mWifiRepository = new FakeWifiRepository(); @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock @@ -121,7 +120,6 @@ public class CarrierTextManagerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mContext.addMockSystemService(WifiManager.class, mWifiManager); mContext.addMockSystemService(PackageManager.class, mPackageManager); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true); mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager); @@ -144,7 +142,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { when(mTelephonyManager.getActiveModemCount()).thenReturn(3); mCarrierTextManager = new CarrierTextManager.Builder( - mContext, mContext.getResources(), mWifiManager, + mContext, mContext.getResources(), mWifiRepository, mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor) .setShowAirplaneMode(true) @@ -364,7 +362,11 @@ public class CarrierTextManagerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn( TelephonyManager.SIM_STATE_READY); when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list); - mockWifi(); + + assertFalse(mWifiRepository.isWifiConnectedWithValidSsid()); + mWifiRepository.setWifiNetwork( + new WifiNetworkModel.Active(0, false, 0, "", false, false, null)); + assertTrue(mWifiRepository.isWifiConnectedWithValidSsid()); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); ServiceState ss = mock(ServiceState.class); @@ -385,13 +387,6 @@ public class CarrierTextManagerTest extends SysuiTestCase { assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText); } - private void mockWifi() { - when(mWifiManager.isWifiEnabled()).thenReturn(true); - WifiInfo wifiInfo = mock(WifiInfo.class); - when(wifiInfo.getBSSID()).thenReturn(""); - when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo); - } - @Test public void testCreateInfo_noSubscriptions() { reset(mCarrierTextCallback); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 2f627cb5d9d7..b9f8dd945293 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -48,8 +48,10 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.plugins.ClockAnimations; import com.android.systemui.plugins.ClockController; import com.android.systemui.plugins.ClockEvents; +import com.android.systemui.plugins.ClockFaceConfig; import com.android.systemui.plugins.ClockFaceController; import com.android.systemui.plugins.ClockFaceEvents; +import com.android.systemui.plugins.ClockTickRate; import com.android.systemui.plugins.log.LogBuffer; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.AnimatableClockView; @@ -185,6 +187,10 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mClockController.getAnimations()).thenReturn(mClockAnimations); when(mClockRegistry.createCurrentClock()).thenReturn(mClockController); when(mClockEventController.getClock()).thenReturn(mClockController); + when(mSmallClockController.getConfig()) + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false)); + when(mLargeClockController.getConfig()) + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false)); mSliceView = new View(getContext()); when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView); @@ -367,6 +373,28 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { } @Test + public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() { + ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = + ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); + when(mSmartspaceController.isEnabled()).thenReturn(true); + when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true); + when(mSmartspaceController.isWeatherEnabled()).thenReturn(true); + mController.init(); + mExecutor.runAllReady(); + assertEquals(View.VISIBLE, mFakeDateView.getVisibility()); + + when(mSmallClockController.getConfig()) + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true)); + when(mLargeClockController.getConfig()) + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true)); + verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture()); + listenerArgumentCaptor.getValue().onCurrentClockChanged(); + + mExecutor.runAllReady(); + assertEquals(View.GONE, mFakeDateView.getVisibility()); + } + + @Test public void testGetClock_nullClock_returnsNull() { when(mClockEventController.getClock()).thenReturn(null); assertNull(mController.getClock()); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 48f7d924667e..2c1d2adb11ee 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -16,7 +16,6 @@ package com.android.keyguard; -import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -24,9 +23,13 @@ import static org.mockito.Mockito.when; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.plugins.ClockConfig; import com.android.systemui.plugins.ClockController; +import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -45,26 +48,21 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class KeyguardStatusViewControllerTest extends SysuiTestCase { - @Mock - private KeyguardStatusView mKeyguardStatusView; - @Mock - private KeyguardSliceViewController mKeyguardSliceViewController; - @Mock - private KeyguardClockSwitchController mKeyguardClockSwitchController; - @Mock - private KeyguardStateController mKeyguardStateController; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - ConfigurationController mConfigurationController; - @Mock - DozeParameters mDozeParameters; - @Mock - ScreenOffAnimationController mScreenOffAnimationController; + @Mock private KeyguardStatusView mKeyguardStatusView; + @Mock private KeyguardSliceViewController mKeyguardSliceViewController; + @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; + @Mock private KeyguardStateController mKeyguardStateController; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private ConfigurationController mConfigurationController; + @Mock private DozeParameters mDozeParameters; + @Mock private ScreenOffAnimationController mScreenOffAnimationController; + @Mock private KeyguardLogger mKeyguardLogger; + @Mock private KeyguardStatusViewController mControllerMock; + @Mock private FeatureFlags mFeatureFlags; + @Mock private InteractionJankMonitor mInteractionJankMonitor; + @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; - @Mock - KeyguardLogger mKeyguardLogger; private KeyguardStatusViewController mController; @@ -81,7 +79,18 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mConfigurationController, mDozeParameters, mScreenOffAnimationController, - mKeyguardLogger); + mKeyguardLogger, + mFeatureFlags, + mInteractionJankMonitor) { + @Override + void setProperty( + AnimatableProperty property, + float value, + boolean animate) { + // Route into the mock version for verification + mControllerMock.setProperty(property, value, animate); + } + }; } @Test @@ -118,10 +127,32 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { } @Test - public void getClock_forwardsToClockSwitch() { + public void updatePosition_primaryClockAnimation() { ClockController mockClock = mock(ClockController.class); when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock); + when(mockClock.getConfig()).thenReturn(new ClockConfig(false, false, true)); + + mController.updatePosition(10, 15, 20f, true); + + verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true); + verify(mKeyguardClockSwitchController).updatePosition( + 10, 20f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true); + verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 1f, true); + verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 1f, true); + } + + @Test + public void updatePosition_alternateClockAnimation() { + ClockController mockClock = mock(ClockController.class); + when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock); + when(mockClock.getConfig()).thenReturn(new ClockConfig(false, true, true)); + + mController.updatePosition(10, 15, 20f, true); - assertEquals(mockClock, mController.getClockController()); + verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true); + verify(mKeyguardClockSwitchController).updatePosition( + 10, 1f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true); + verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true); + verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 08813a7fb48a..3eb9590c0b95 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -20,9 +20,12 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; +import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN; import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; @@ -41,6 +44,8 @@ import static com.android.systemui.statusbar.policy.DevicePostureController.DEVI import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -79,7 +84,6 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; -import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; @@ -283,33 +287,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Before public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); - - mFaceSensorProperties = - List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false)); - when(mFaceManager.isHardwareDetected()).thenReturn(true); - when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true); - when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties); when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId); - mFingerprintSensorProperties = List.of( - new FingerprintSensorPropertiesInternal(1 /* sensorId */, - FingerprintSensorProperties.STRENGTH_STRONG, - 1 /* maxEnrollmentsPerUser */, - List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, - "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)), - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - false /* resetLockoutRequiresHAT */)); - when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); - when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn( - mFingerprintSensorProperties); when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true); when(mUserManager.isPrimaryUser()).thenReturn(true); when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class)); when(mStrongAuthTracker - .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */)) + .isUnlockingWithBiometricAllowed(anyBoolean() /* isClass3Biometric */)) .thenReturn(true); when(mTelephonyManager.getServiceStateForSubscriber(anyInt())) .thenReturn(new ServiceState()); @@ -346,20 +330,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { anyInt()); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); - - ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor = - ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class); - verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture()); - mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue(); - mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); - - ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor = - ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class); - verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( - fingerprintCaptor.capture()); - mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue(); - mFingerprintAuthenticatorsRegisteredCallback - .onAllAuthenticatorsRegistered(mFingerprintSensorProperties); + captureAuthenticatorsRegisteredCallbacks(); + setupFaceAuth(/* isClass3 */ false); + setupFingerprintAuth(/* isClass3 */ true); verify(mBiometricManager) .registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture()); @@ -381,8 +354,64 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true); } + private void captureAuthenticatorsRegisteredCallbacks() throws RemoteException { + ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor = + ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class); + verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture()); + mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue(); + mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); + + ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor = + ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class); + verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( + fingerprintCaptor.capture()); + mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue(); + mFingerprintAuthenticatorsRegisteredCallback + .onAllAuthenticatorsRegistered(mFingerprintSensorProperties); + } + + private void setupFaceAuth(boolean isClass3) throws RemoteException { + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true); + mFaceSensorProperties = + List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false, isClass3)); + when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties); + mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); + assertEquals(isClass3, mKeyguardUpdateMonitor.isFaceClass3()); + } + + private void setupFingerprintAuth(boolean isClass3) throws RemoteException { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + mFingerprintSensorProperties = List.of( + createFingerprintSensorPropertiesInternal(TYPE_UDFPS_OPTICAL, isClass3)); + when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn( + mFingerprintSensorProperties); + mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered( + mFingerprintSensorProperties); + assertEquals(isClass3, mKeyguardUpdateMonitor.isFingerprintClass3()); + } + + private FingerprintSensorPropertiesInternal createFingerprintSensorPropertiesInternal( + @FingerprintSensorProperties.SensorType int sensorType, + boolean isClass3) { + final List<ComponentInfoInternal> componentInfo = + List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */, + "vendor/model/revision" /* hardwareVersion */, + "1.01" /* firmwareVersion */, + "00000001" /* serialNumber */, "" /* softwareVersion */)); + return new FingerprintSensorPropertiesInternal( + FINGERPRINT_SENSOR_ID, + isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE, + 1 /* maxEnrollmentsPerUser */, + componentInfo, + sensorType, + true /* resetLockoutRequiresHardwareAuthToken */); + } + @NonNull - private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) { + private FaceSensorPropertiesInternal createFaceSensorProperties( + boolean supportsFaceDetection, boolean isClass3) { final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, @@ -391,10 +420,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, "vendor/version/revision" /* softwareVersion */)); - return new FaceSensorPropertiesInternal( - 0 /* id */, - FaceSensorProperties.STRENGTH_STRONG, + FACE_SENSOR_ID /* id */, + isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE, 1 /* maxTemplatesAllowed */, componentInfo, FaceSensorProperties.TYPE_UNKNOWN, @@ -686,7 +714,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() { // GIVEN unlocking with biometric is allowed - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); // THEN unlocking with face and fp is allowed Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -706,12 +734,31 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testUnlockingWithFaceAllowed_fingerprintLockout() { - // GIVEN unlocking with biometric is allowed - strongAuthNotRequired(); + public void class3FingerprintLockOut_lockOutClass1Face() throws RemoteException { + setupFaceAuth(/* isClass3 */ false); + setupFingerprintAuth(/* isClass3 */ true); + + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN fingerprint (class 3) is lock out + fingerprintErrorTemporaryLockOut(); + + // THEN unlocking with face is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FACE)); + } + + @Test + public void class3FingerprintLockOut_lockOutClass3Face() throws RemoteException { + setupFaceAuth(/* isClass3 */ true); + setupFingerprintAuth(/* isClass3 */ true); - // WHEN fingerprint is locked out - fingerprintErrorTemporaryLockedOut(); + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN fingerprint (class 3) is lock out + fingerprintErrorTemporaryLockOut(); // THEN unlocking with face is not allowed Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -719,6 +766,38 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void class3FaceLockOut_lockOutClass3Fingerprint() throws RemoteException { + setupFaceAuth(/* isClass3 */ true); + setupFingerprintAuth(/* isClass3 */ true); + + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN face (class 3) is lock out + faceAuthLockOut(); + + // THEN unlocking with fingerprint is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)); + } + + @Test + public void class1FaceLockOut_doesNotLockOutClass3Fingerprint() throws RemoteException { + setupFaceAuth(/* isClass3 */ false); + setupFingerprintAuth(/* isClass3 */ true); + + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN face (class 1) is lock out + faceAuthLockOut(); + + // THEN unlocking with fingerprint is still allowed + Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)); + } + + @Test public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() { // GIVEN unlocking with biometric is not allowed when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); @@ -731,10 +810,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testUnlockingWithFpAllowed_fingerprintLockout() { // GIVEN unlocking with biometric is allowed - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); - // WHEN fingerprint is locked out - fingerprintErrorTemporaryLockedOut(); + // WHEN fingerprint is lock out + fingerprintErrorTemporaryLockOut(); // THEN unlocking with fingerprint is not allowed Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -757,8 +836,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null); Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); - // WHEN fingerprint is locked out - fingerprintErrorTemporaryLockedOut(); + // WHEN fingerprint is lock out + fingerprintErrorTemporaryLockOut(); // THEN user is NOT considered as "having trust" and bouncer cannot be skipped Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); @@ -826,7 +905,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // GIVEN udfps is supported and strong auth required for weak biometrics (face) only givenUdfpsSupported(); - strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face + primaryAuthRequiredForWeakBiometricOnly(); // allows class3 fp to run but not class1 face // WHEN the device wakes up mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); @@ -863,7 +942,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() { // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required lockscreenBypassIsAllowed(); - strongAuthRequiredEncrypted(); + primaryAuthRequiredEncrypted(); keyguardIsVisible(); // WHEN the device wakes up @@ -931,6 +1010,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse(); verifyFingerprintAuthenticateNeverCalled(); // WHEN alternate bouncer is shown + mKeyguardUpdateMonitor.setKeyguardShowing(true, true); mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); // THEN make sure FP listening begins @@ -1011,10 +1091,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() { - // test whether face will be skipped if authenticated, so the value of isStrongBiometric + // test whether face will be skipped if authenticated, so the value of isClass3Biometric // doesn't matter here mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(), - true /* isStrongBiometric */); + true /* isClass3Biometric */); setKeyguardBouncerVisibility(true); mTestableLooper.processAllMessages(); @@ -1027,7 +1107,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); keyguardIsVisible(); - faceAuthLockedOut(); + faceAuthLockOut(); verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt()); } @@ -1050,7 +1130,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); keyguardIsVisible(); - faceAuthLockedOut(); + faceAuthLockOut(); mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, ""); @@ -1060,32 +1140,32 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testGetUserCanSkipBouncer_whenFace() { int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isStrongBiometric */); + mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @Test public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() { - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */)) + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */)) .thenReturn(false); int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isStrongBiometric */); + mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse(); } @Test public void testGetUserCanSkipBouncer_whenFingerprint() { int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isStrongBiometric */); + mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @Test public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() { - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */)) + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */)) .thenReturn(false); int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isStrongBiometric */); + mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse(); } @@ -1126,9 +1206,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @BiometricConstants.LockoutMode int fingerprintLockoutMode, @BiometricConstants.LockoutMode int faceLockoutMode) { final int newUser = 12; - final boolean faceLocked = + final boolean faceLockOut = faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE; - final boolean fpLocked = + final boolean fpLockOut = fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE; mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); @@ -1161,8 +1241,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { eq(false), eq(BiometricSourceType.FINGERPRINT)); // THEN locked out states are updated - assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked); - assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked); + assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLockOut); + assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLockOut); // Fingerprint should be cancelled on lockout if going to lockout state, else // restarted if it's not @@ -1443,7 +1523,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { throws RemoteException { // GIVEN SFPS supported and enrolled final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); - props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON)); + props.add(createFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON, + /* isClass3 */ true)); when(mAuthController.getSfpsProps()).thenReturn(props); when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); @@ -1466,17 +1547,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); } - private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal( - @FingerprintSensorProperties.SensorType int sensorType) { - return new FingerprintSensorPropertiesInternal( - 0 /* sensorId */, - SensorProperties.STRENGTH_STRONG, - 1 /* maxEnrollmentsPerUser */, - new ArrayList<ComponentInfoInternal>(), - sensorType, - true /* resetLockoutRequiresHardwareAuthToken */); - } - @Test public void testShouldNotListenForUdfps_whenTrustEnabled() { // GIVEN a "we should listen for udfps" state @@ -1613,7 +1683,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { keyguardNotGoingAway(); occludingAppRequestsFaceAuth(); currentUserIsPrimary(); - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); biometricsEnabledForCurrentUser(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1622,7 +1692,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); // Fingerprint is locked out. - fingerprintErrorTemporaryLockedOut(); + fingerprintErrorTemporaryLockOut(); assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); } @@ -1634,7 +1704,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { bouncerFullyVisibleAndNotGoingToSleep(); keyguardNotGoingAway(); currentUserIsPrimary(); - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); biometricsEnabledForCurrentUser(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1657,7 +1727,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { bouncerFullyVisibleAndNotGoingToSleep(); keyguardNotGoingAway(); currentUserIsPrimary(); - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); biometricsEnabledForCurrentUser(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1684,7 +1754,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); biometricsEnabledForCurrentUser(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1940,7 +2010,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); // Face is locked out. - faceAuthLockedOut(); + faceAuthLockOut(); mTestableLooper.processAllMessages(); // This is needed beccause we want to show face locked out error message whenever face auth @@ -2578,7 +2648,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture()); // GIVEN device is locked out - fingerprintErrorTemporaryLockedOut(); + fingerprintErrorTemporaryLockOut(); // GIVEN FP detection is running givenDetectFingerprintWithClearingFingerprintManagerInvocations(); @@ -2662,6 +2732,21 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { KeyguardUpdateMonitor.getCurrentUser())).isTrue(); } + @Test + public void testFingerprintListeningStateWhenOccluded() { + when(mAuthController.isUdfpsSupported()).thenReturn(true); + + mKeyguardUpdateMonitor.setKeyguardShowing(false, false); + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_BIOMETRIC); + mKeyguardUpdateMonitor.setKeyguardShowing(false, true); + + verifyFingerprintAuthenticateNeverCalled(); + + mKeyguardUpdateMonitor.setKeyguardShowing(true, true); + mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); + + verifyFingerprintAuthenticateCall(); + } private void verifyFingerprintAuthenticateNeverCalled() { verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any()); @@ -2705,8 +2790,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void supportsFaceDetection() throws RemoteException { + final boolean isClass3 = !mFaceSensorProperties.isEmpty() + && mFaceSensorProperties.get(0).sensorStrength == STRENGTH_STRONG; mFaceSensorProperties = - List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true)); + List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true, isClass3)); mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); } @@ -2734,7 +2821,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } } - private void faceAuthLockedOut() { + private void faceAuthLockOut() { mKeyguardUpdateMonitor.mFaceAuthenticationCallback .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); } @@ -2767,7 +2854,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.setSwitchingUser(true); } - private void fingerprintErrorTemporaryLockedOut() { + private void fingerprintErrorTemporaryLockOut() { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out"); } @@ -2821,18 +2908,18 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ); } - private void strongAuthRequiredEncrypted() { + private void primaryAuthRequiredEncrypted() { when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); } - private void strongAuthRequiredForWeakBiometricOnly() { + private void primaryAuthRequiredForWeakBiometricOnly() { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false); } - private void strongAuthNotRequired() { + private void primaryAuthNotRequiredByStrongAuthTracker() { when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) .thenReturn(0); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); @@ -2917,10 +3004,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void givenDetectFace() throws RemoteException { - // GIVEN bypass is enabled, face detection is supported and strong auth is required + // GIVEN bypass is enabled, face detection is supported and primary auth is required lockscreenBypassIsAllowed(); supportsFaceDetection(); - strongAuthRequiredEncrypted(); + primaryAuthRequiredEncrypted(); keyguardIsVisible(); // fingerprint is NOT running, UDFPS is NOT supported diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt index 64fec5bfd4ed..dea2082a2c22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.shade +package com.android.keyguard import android.animation.Animator import android.testing.AndroidTestingRunner import android.transition.TransitionValues import androidx.test.filters.SmallTest -import com.android.keyguard.KeyguardStatusViewController +import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter import com.android.systemui.SysuiTestCase -import com.android.systemui.shade.NotificationPanelViewController.SplitShadeTransitionAdapter import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -33,14 +32,14 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) class SplitShadeTransitionAdapterTest : SysuiTestCase() { - @Mock private lateinit var keyguardStatusViewController: KeyguardStatusViewController + @Mock private lateinit var KeyguardClockSwitchController: KeyguardClockSwitchController private lateinit var adapter: SplitShadeTransitionAdapter @Before fun setUp() { MockitoAnnotations.initMocks(this) - adapter = SplitShadeTransitionAdapter(keyguardStatusViewController) + adapter = SplitShadeTransitionAdapter(KeyguardClockSwitchController) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt deleted file mode 100644 index 44da5f42aafd..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt +++ /dev/null @@ -1,255 +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 - -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.content.res.Resources -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.broadcast.BroadcastSender -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.kotlinArgumentCaptor -import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat -import java.io.File -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.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyZeroInteractions -import org.mockito.MockitoAnnotations - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class ChooserPinMigrationTest : SysuiTestCase() { - - private val fakeFeatureFlags = FakeFeatureFlags() - private val fakePreferences = - mutableMapOf( - "TestPinnedPackage/TestPinnedClass" to true, - "TestUnpinnedPackage/TestUnpinnedClass" to false, - ) - private val intent = kotlinArgumentCaptor<Intent>() - private val permission = kotlinArgumentCaptor<String>() - - private lateinit var chooserPinMigration: ChooserPinMigration - - @Mock private lateinit var mockContext: Context - @Mock private lateinit var mockResources: Resources - @Mock - private lateinit var mockLegacyPinPrefsFileSupplier: - ChooserPinMigration.Companion.LegacyPinPrefsFileSupplier - @Mock private lateinit var mockFile: File - @Mock private lateinit var mockSharedPreferences: SharedPreferences - @Mock private lateinit var mockSharedPreferencesEditor: SharedPreferences.Editor - @Mock private lateinit var mockBroadcastSender: BroadcastSender - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - - whenever(mockContext.resources).thenReturn(mockResources) - whenever(mockContext.getSharedPreferences(any<File>(), anyInt())) - .thenReturn(mockSharedPreferences) - whenever(mockResources.getString(anyInt())).thenReturn("TestPackage/TestClass") - whenever(mockSharedPreferences.all).thenReturn(fakePreferences) - whenever(mockSharedPreferences.edit()).thenReturn(mockSharedPreferencesEditor) - whenever(mockSharedPreferencesEditor.commit()).thenReturn(true) - whenever(mockLegacyPinPrefsFileSupplier.get()).thenReturn(mockFile) - whenever(mockFile.exists()).thenReturn(true) - whenever(mockFile.delete()).thenReturn(true) - fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, true) - } - - @Test - fun start_performsMigration() { - // Arrange - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture()) - assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION") - assertThat(intent.value.`package`).isEqualTo("TestPackage") - assertThat(intent.value.extras?.keySet()).hasSize(2) - assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue() - assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false)) - .isTrue() - assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue() - assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true)) - .isFalse() - assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION") - - // Assert - verify(mockSharedPreferencesEditor).clear() - verify(mockSharedPreferencesEditor).commit() - - // Assert - verify(mockFile).delete() - } - - @Test - fun start_doesNotDeleteLegacyPreferencesFile_whenClearingItFails() { - // Arrange - whenever(mockSharedPreferencesEditor.commit()).thenReturn(false) - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture()) - assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION") - assertThat(intent.value.`package`).isEqualTo("TestPackage") - assertThat(intent.value.extras?.keySet()).hasSize(2) - assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue() - assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false)) - .isTrue() - assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue() - assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true)) - .isFalse() - assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION") - - // Assert - verify(mockSharedPreferencesEditor).clear() - verify(mockSharedPreferencesEditor).commit() - - // Assert - verify(mockFile, never()).delete() - } - - @Test - fun start_OnlyDeletesLegacyPreferencesFile_whenEmpty() { - // Arrange - whenever(mockSharedPreferences.all).thenReturn(emptyMap()) - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verifyZeroInteractions(mockBroadcastSender) - - // Assert - verifyZeroInteractions(mockSharedPreferencesEditor) - - // Assert - verify(mockFile).delete() - } - - @Test - fun start_DoesNotDoMigration_whenFlagIsDisabled() { - // Arrange - fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, false) - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verifyZeroInteractions(mockBroadcastSender) - - // Assert - verifyZeroInteractions(mockSharedPreferencesEditor) - - // Assert - verify(mockFile, never()).delete() - } - - @Test - fun start_DoesNotDoMigration_whenLegacyPreferenceFileNotPresent() { - // Arrange - whenever(mockFile.exists()).thenReturn(false) - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verifyZeroInteractions(mockBroadcastSender) - - // Assert - verifyZeroInteractions(mockSharedPreferencesEditor) - - // Assert - verify(mockFile, never()).delete() - } - - @Test - fun start_DoesNotDoMigration_whenConfiguredChooserComponentIsInvalid() { - // Arrange - whenever(mockResources.getString(anyInt())).thenReturn("InvalidComponent") - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verifyZeroInteractions(mockBroadcastSender) - - // Assert - verifyZeroInteractions(mockSharedPreferencesEditor) - - // Assert - verify(mockFile, never()).delete() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt new file mode 100644 index 000000000000..01d3a3931052 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt @@ -0,0 +1,119 @@ +/* + * 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 + +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.filters.SmallTest +import com.android.internal.R +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.biometrics.AuthController +import com.android.systemui.decor.FaceScanningProviderFactory +import com.android.systemui.dump.logcatLogBuffer +import com.android.systemui.log.ScreenDecorationsLogger +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations + +@RunWithLooper +@RunWith(AndroidTestingRunner::class) +@SmallTest +class FaceScanningProviderFactoryTest : SysuiTestCase() { + + private lateinit var underTest: FaceScanningProviderFactory + + @Mock private lateinit var authController: AuthController + + @Mock private lateinit var statusBarStateController: StatusBarStateController + + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + + @Mock private lateinit var display: Display + + private val displayId = 2 + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + val displayInfo = DisplayInfo() + val dmGlobal = mock(DisplayManagerGlobal::class.java) + val display = + Display( + dmGlobal, + displayId, + displayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS + ) + whenever(dmGlobal.getDisplayInfo(eq(displayId))).thenReturn(displayInfo) + val displayContext = context.createDisplayContext(display) as SysuiTestableContext + displayContext.orCreateTestableResources.addOverride( + R.array.config_displayUniqueIdArray, + arrayOf(displayId) + ) + displayContext.orCreateTestableResources.addOverride( + R.bool.config_fillMainBuiltInDisplayCutout, + true + ) + underTest = + FaceScanningProviderFactory( + authController, + displayContext, + statusBarStateController, + keyguardUpdateMonitor, + mock(Executor::class.java), + ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")) + ) + + whenever(authController.faceSensorLocation).thenReturn(Point(10, 10)) + } + + @Test + fun shouldNotShowFaceScanningAnimationIfFaceIsNotEnrolled() { + whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) + whenever(authController.isShowing).thenReturn(true) + + assertThat(underTest.shouldShowFaceScanningAnim()).isFalse() + } + + @Test + fun shouldShowFaceScanningAnimationIfBiometricPromptIsShowing() { + whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) + whenever(authController.isShowing).thenReturn(true) + + assertThat(underTest.shouldShowFaceScanningAnim()).isTrue() + } + + @Test + fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() { + whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) + whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true) + + assertThat(underTest.shouldShowFaceScanningAnim()).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 0978c824cb15..9c36af3a35e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -18,6 +18,7 @@ package com.android.systemui.accessibility; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.view.Choreographer.FrameCallback; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; @@ -172,6 +173,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { returnsSecondArg()); mResources = getContext().getOrCreateTestableResources().getResources(); + // prevent the config orientation from undefined, which may cause config.diff method + // neglecting the orientation update. + if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) { + mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT; + } + mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( mContext, mValueAnimator); mWindowMagnificationController = @@ -688,7 +695,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void enableWindowMagnification_rotationIsChanged_updateRotationValue() { - final Configuration config = mContext.getResources().getConfiguration(); + // the config orientation should not be undefined, since it would cause config.diff + // returning 0 and thus the orientation changed would not be detected + assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation); + + final Configuration config = mResources.getConfiguration(); config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; final int newRotation = simulateRotateTheDevice(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt index 921f9a8fc7d6..2b95973363ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt @@ -37,7 +37,13 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() { @Test fun onBackProgressed_shouldInvoke_onBackProgress() { - val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT) + val backEvent = + BackEvent( + /* touchX = */ 0f, + /* touchY = */ 0f, + /* progress = */ 0f, + /* swipeEdge = */ BackEvent.EDGE_LEFT + ) onBackAnimationCallback.onBackStarted(backEvent) onBackAnimationCallback.onBackProgressed(backEvent) @@ -47,7 +53,13 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() { @Test fun onBackStarted_shouldInvoke_onBackStart() { - val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT) + val backEvent = + BackEvent( + /* touchX = */ 0f, + /* touchY = */ 0f, + /* progress = */ 0f, + /* swipeEdge = */ BackEvent.EDGE_LEFT + ) onBackAnimationCallback.onBackStarted(backEvent) diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index 8cb91304808d..4cb99a23a531 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -120,7 +120,6 @@ public class BrightLineClassifierTest extends SysuiTestCase { gestureCompleteListenerCaptor.capture()); mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue(); - mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true); mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true); } @@ -260,13 +259,6 @@ public class BrightLineClassifierTest extends SysuiTestCase { } @Test - public void testIsFalseLongTap_FalseLongTap_NotFlagged() { - mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false); - when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult); - assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse(); - } - - @Test public void testIsFalseLongTap_FalseLongTap() { when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult); assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 315774aad71a..292fdff0027d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -94,7 +94,6 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, mAccessibilityManager, false, mFakeFeatureFlags); - mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java index 08427dab978b..21397d97b578 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java @@ -269,6 +269,30 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { } @Test + public void testInputEventPropagationAfterRemoval() { + final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class); + + final Environment environment = new Environment(Stream.of(touchHandler) + .collect(Collectors.toCollection(HashSet::new))); + + final InputEvent initialEvent = Mockito.mock(InputEvent.class); + environment.publishInputEvent(initialEvent); + + // Ensure session started + final DreamTouchHandler.TouchSession session = captureSession(touchHandler); + final InputChannelCompat.InputEventListener eventListener = + registerInputEventListener(session); + + session.pop(); + environment.executeAll(); + + final InputEvent event = Mockito.mock(InputEvent.class); + environment.publishInputEvent(event); + + verify(eventListener, never()).onInputEvent(eq(event)); + } + + @Test public void testInputGesturePropagation() { final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 6e002f5a9a9a..2489e043c7db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -59,11 +59,10 @@ import com.android.systemui.statusbar.phone.FakeKeyguardStateController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.KotlinArgumentCaptor +import com.android.systemui.util.mockito.captureMany import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.SystemClock import com.google.common.truth.Truth.assertThat -import java.io.PrintWriter -import java.io.StringWriter import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.StandardTestDispatcher @@ -81,6 +80,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.isNull import org.mockito.Mockito.mock @@ -88,6 +88,8 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations +import java.io.PrintWriter +import java.io.StringWriter @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -120,6 +122,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private lateinit var authStatus: FlowValue<AuthenticationStatus?> private lateinit var detectStatus: FlowValue<DetectionStatus?> private lateinit var authRunning: FlowValue<Boolean?> + private lateinit var bypassEnabled: FlowValue<Boolean?> private lateinit var lockedOut: FlowValue<Boolean?> private lateinit var canFaceAuthRun: FlowValue<Boolean?> private lateinit var authenticated: FlowValue<Boolean?> @@ -726,6 +729,23 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test + fun isBypassEnabledReflectsBypassControllerState() = + testScope.runTest { + initCollectors() + runCurrent() + val listeners = captureMany { + verify(bypassController, atLeastOnce()) + .registerOnBypassStateChangedListener(capture()) + } + + listeners.forEach { it.onBypassStateChanged(true) } + assertThat(bypassEnabled()).isTrue() + + listeners.forEach { it.onBypassStateChanged(false) } + assertThat(bypassEnabled()).isFalse() + } + + @Test fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() = testScope.runTest { testGatingCheckForDetect { @@ -844,6 +864,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { lockedOut = collectLastValue(underTest.isLockedOut) canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth) authenticated = collectLastValue(underTest.isAuthenticated) + bypassEnabled = collectLastValue(underTest.isBypassEnabled) fakeUserRepository.setSelectedUserInfo(primaryUser) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index aace5661862b..1e465c791795 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -24,6 +24,7 @@ import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color @@ -42,6 +43,7 @@ import android.os.Bundle import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.animation.Interpolator @@ -101,7 +103,6 @@ import dagger.Lazy import junit.framework.Assert.assertTrue import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -2200,8 +2201,7 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test - @Ignore("b/276920368") - fun bindRecommendation_carouselNotFitThreeRecs() { + fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() { useRealConstraintSets() setupUpdatedRecommendationViewHolder() val albumArt = getColorIcon(Color.RED) @@ -2229,16 +2229,84 @@ public class MediaControlPanelTest : SysuiTestCase() { // set the screen width less than the width of media controls. player.context.resources.configuration.screenWidthDp = 350 + player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT player.attachRecommendation(recommendationViewHolder) player.bindRecommendation(data) - assertThat(player.numberOfFittedRecommendations).isEqualTo(2) - assertThat(expandedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(collapsedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(expandedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(collapsedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(expandedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE) + val res = player.context.resources + val displayAvailableWidth = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt() + val recCoverWidth: Int = + (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) + + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2) + val numOfRecs = displayAvailableWidth / recCoverWidth + + assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs) + recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container -> + if (index < numOfRecs) { + assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE) + assertThat(collapsedSet.getVisibility(container.id)) + .isEqualTo(ConstraintSet.VISIBLE) + } else { + assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) + assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) + } + } + } + + @Test + fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() { + useRealConstraintSets() + setupUpdatedRecommendationViewHolder() + val albumArt = getColorIcon(Color.RED) + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", "title3") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build() + ) + ) + + // set the screen width less than the width of media controls. + // We should have dp width less than 378 to test. In landscape we should have 2x. + player.context.resources.configuration.screenWidthDp = 700 + player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(data) + + val res = player.context.resources + val displayAvailableWidth = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt() + val recCoverWidth: Int = + (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) + + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2) + val numOfRecs = displayAvailableWidth / recCoverWidth + + assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs) + recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container -> + if (index < numOfRecs) { + assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE) + assertThat(collapsedSet.getVisibility(container.id)) + .isEqualTo(ConstraintSet.VISIBLE) + } else { + assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) + assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) + } + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 34d2b14d46a9..e4d8b2598fe3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -46,13 +46,14 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.internal.util.CollectionUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.nano.SystemUIProtoDump; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.qs.QSFactory; @@ -62,7 +63,6 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServiceKey; -import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserFileManager; @@ -110,25 +110,17 @@ public class QSTileHostTest extends SysuiTestCase { @Mock private Provider<AutoTileManager> mAutoTiles; @Mock - private DumpManager mDumpManager; - @Mock private CentralSurfaces mCentralSurfaces; @Mock private QSLogger mQSLogger; @Mock private CustomTile mCustomTile; @Mock - private UiEventLogger mUiEventLogger; - @Mock private UserTracker mUserTracker; private SecureSettings mSecureSettings; @Mock private CustomTileStatePersister mCustomTileStatePersister; @Mock - private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder; - @Mock - private TileServiceRequestController mTileServiceRequestController; - @Mock private TileLifecycleManager.Factory mTileLifecycleManagerFactory; @Mock private TileLifecycleManager mTileLifecycleManager; @@ -137,6 +129,8 @@ public class QSTileHostTest extends SysuiTestCase { private SparseArray<SharedPreferences> mSharedPreferencesByUser; + private FakeFeatureFlags mFeatureFlags; + private FakeExecutor mMainExecutor; private QSTileHost mQSTileHost; @@ -144,12 +138,13 @@ public class QSTileHostTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + mFeatureFlags = new FakeFeatureFlags(); + + mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false); + mMainExecutor = new FakeExecutor(new FakeSystemClock()); mSharedPreferencesByUser = new SparseArray<>(); - - when(mTileServiceRequestControllerBuilder.create(any())) - .thenReturn(mTileServiceRequestController); when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class))) .thenReturn(mTileLifecycleManager); when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) @@ -165,10 +160,9 @@ public class QSTileHostTest extends SysuiTestCase { mSecureSettings = new FakeSettings(); saveSetting(""); mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor, - mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces, - mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, - mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory, - mUserFileManager); + mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces, + mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, + mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags); mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) { @Override @@ -686,18 +680,16 @@ public class QSTileHostTest extends SysuiTestCase { TestQSTileHost(Context context, QSFactory defaultFactory, Executor mainExecutor, PluginManager pluginManager, TunerService tunerService, - Provider<AutoTileManager> autoTiles, DumpManager dumpManager, - CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger, + Provider<AutoTileManager> autoTiles, + CentralSurfaces centralSurfaces, QSLogger qsLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, - TileServiceRequestController.Builder tileServiceRequestControllerBuilder, TileLifecycleManager.Factory tileLifecycleManagerFactory, - UserFileManager userFileManager) { + UserFileManager userFileManager, FeatureFlags featureFlags) { super(context, defaultFactory, mainExecutor, pluginManager, - tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger, - uiEventLogger, userTracker, secureSettings, customTileStatePersister, - tileServiceRequestControllerBuilder, tileLifecycleManagerFactory, - userFileManager); + tunerService, autoTiles, Optional.of(centralSurfaces), qsLogger, + userTracker, secureSettings, customTileStatePersister, + tileLifecycleManagerFactory, userFileManager, featureFlags); } @Override @@ -715,6 +707,7 @@ public class QSTileHostTest extends SysuiTestCase { protected TestTile(QSHost host) { super( host, + mock(QsEventLogger.class), mock(Looper.class), mock(Handler.class), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt new file mode 100644 index 000000000000..40aa2607dc94 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt @@ -0,0 +1,35 @@ +/* + * 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.qs + +import com.android.internal.logging.InstanceId +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.InstanceIdSequenceFake + +class QsEventLoggerFake( + uiEventLogger: UiEventLoggerFake, + private val instanceIdSequence: InstanceIdSequenceFake, +) : QsEventLogger, UiEventLogger by uiEventLogger { + + val lastInstanceId: Int + get() = instanceIdSequence.lastInstanceId + + override fun getNewInstanceId(): InstanceId { + return instanceIdSequence.newInstanceId() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index ac106ef9bf51..198ed4ac08fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.util.mockito.any @@ -56,12 +57,12 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -89,6 +90,7 @@ class CustomTileTest : SysuiTestCase() { @Mock private lateinit var applicationInfo: ApplicationInfo @Mock private lateinit var serviceInfo: ServiceInfo @Mock private lateinit var customTileStatePersister: CustomTileStatePersister + @Mock private lateinit var uiEventLogger: QsEventLogger private var displayTracker = FakeDisplayTracker(mContext) private lateinit var customTile: CustomTile @@ -115,6 +117,7 @@ class CustomTileTest : SysuiTestCase() { customTileBuilder = CustomTile.Builder( { tileHost }, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt index c03849b35f54..50a8d2630d79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt @@ -170,6 +170,21 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { } @Test + fun addTileAtPosition_tooLarge_addedAtEnd() = + testScope.runTest { + val tiles by collectLastValue(underTest.tilesSpecs(0)) + + val specs = "a,custom(b/c)" + storeTilesForUser(specs, 0) + + underTest.addTile(userId = 0, TileSpec.create("d"), position = 100) + + val expected = "a,custom(b/c),d" + assertThat(loadTilesForUser(0)).isEqualTo(expected) + assertThat(tiles).isEqualTo(expected.toTileSpecs()) + } + + @Test fun addTileForOtherUser_addedInThatUser() = testScope.runTest { val tilesUser0 by collectLastValue(underTest.tilesSpecs(0)) @@ -187,27 +202,27 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { } @Test - fun removeTile() = + fun removeTiles() = testScope.runTest { val tiles by collectLastValue(underTest.tilesSpecs(0)) storeTilesForUser("a,b", 0) - underTest.removeTile(userId = 0, TileSpec.create("a")) + underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"))) assertThat(loadTilesForUser(0)).isEqualTo("b") assertThat(tiles).isEqualTo("b".toTileSpecs()) } @Test - fun removeTileNotThere_noop() = + fun removeTilesNotThere_noop() = testScope.runTest { val tiles by collectLastValue(underTest.tilesSpecs(0)) val specs = "a,b" storeTilesForUser(specs, 0) - underTest.removeTile(userId = 0, TileSpec.create("c")) + underTest.removeTiles(userId = 0, listOf(TileSpec.create("c"))) assertThat(loadTilesForUser(0)).isEqualTo(specs) assertThat(tiles).isEqualTo(specs.toTileSpecs()) @@ -221,7 +236,7 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { val specs = "a,b" storeTilesForUser(specs, 0) - underTest.removeTile(userId = 0, TileSpec.Invalid) + underTest.removeTiles(userId = 0, listOf(TileSpec.Invalid)) assertThat(loadTilesForUser(0)).isEqualTo(specs) assertThat(tiles).isEqualTo(specs.toTileSpecs()) @@ -237,7 +252,7 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { storeTilesForUser(specs, 0) storeTilesForUser(specs, 1) - underTest.removeTile(userId = 1, TileSpec.create("a")) + underTest.removeTiles(userId = 1, listOf(TileSpec.create("a"))) assertThat(loadTilesForUser(0)).isEqualTo(specs) assertThat(user0Tiles).isEqualTo(specs.toTileSpecs()) @@ -246,6 +261,19 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { } @Test + fun removeMultipleTiles() = + testScope.runTest { + val tiles by collectLastValue(underTest.tilesSpecs(0)) + + storeTilesForUser("a,b,c,d", 0) + + underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"), TileSpec.create("c"))) + + assertThat(loadTilesForUser(0)).isEqualTo("b,d") + assertThat(tiles).isEqualTo("b,d".toTileSpecs()) + } + + @Test fun changeTiles() = testScope.runTest { val tiles by collectLastValue(underTest.tilesSpecs(0)) @@ -310,8 +338,8 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { storeTilesForUser(specs, 0) coroutineScope { - underTest.removeTile(userId = 0, TileSpec.create("c")) - underTest.removeTile(userId = 0, TileSpec.create("a")) + underTest.removeTiles(userId = 0, listOf(TileSpec.create("c"))) + underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"))) } assertThat(loadTilesForUser(0)).isEqualTo("b") diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt new file mode 100644 index 000000000000..7ecb4dc9376a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -0,0 +1,674 @@ +/* + * 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.qs.pipeline.domain.interactor + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.UserInfo +import android.os.UserHandle +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dump.nano.SystemUIProtoDump +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.QSTile.BooleanState +import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.external.CustomTile +import com.android.systemui.qs.external.CustomTileStatePersister +import com.android.systemui.qs.external.TileLifecycleManager +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.qs.pipeline.domain.model.TileModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.qs.toProto +import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.repository.FakeUserRepository +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 com.google.protobuf.nano.MessageNano +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +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.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@OptIn(ExperimentalCoroutinesApi::class) +class CurrentTilesInteractorImplTest : SysuiTestCase() { + + private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository() + private val userRepository = FakeUserRepository() + private val tileFactory = FakeQSFactory(::tileCreator) + private val customTileAddedRepository: CustomTileAddedRepository = + FakeCustomTileAddedRepository() + private val featureFlags = FakeFeatureFlags() + private val tileLifecycleManagerFactory = TLMFactory() + + @Mock private lateinit var customTileStatePersister: CustomTileStatePersister + + @Mock private lateinit var userTracker: UserTracker + + @Mock private lateinit var logger: QSPipelineLogger + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val unavailableTiles = mutableSetOf("e") + + private lateinit var underTest: CurrentTilesInteractorImpl + + @OptIn(ExperimentalCoroutinesApi::class) + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true) + + userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1)) + setUserTracker(0) + + underTest = + CurrentTilesInteractorImpl( + tileSpecRepository = tileSpecRepository, + userRepository = userRepository, + customTileStatePersister = customTileStatePersister, + tileFactory = tileFactory, + customTileAddedRepository = customTileAddedRepository, + tileLifecycleManagerFactory = tileLifecycleManagerFactory, + userTracker = userTracker, + mainDispatcher = testDispatcher, + backgroundDispatcher = testDispatcher, + scope = testScope.backgroundScope, + logger = logger, + featureFlags = featureFlags, + ) + } + + @Test + fun initialState() = + testScope.runTest(USER_INFO_0) { + assertThat(underTest.currentTiles.value).isEmpty() + assertThat(underTest.currentQSTiles).isEmpty() + assertThat(underTest.currentTilesSpecs).isEmpty() + assertThat(underTest.userId.value).isEqualTo(0) + assertThat(underTest.userContext.value.userId).isEqualTo(0) + } + + @Test + fun correctTiles() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = + listOf( + TileSpec.create("a"), + TileSpec.create("e"), + CUSTOM_TILE_SPEC, + TileSpec.create("d"), + TileSpec.create("non_existent") + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + // check each tile + + // Tile a + val tile0 = tiles!![0] + assertThat(tile0.spec).isEqualTo(specs[0]) + assertThat(tile0.tile.tileSpec).isEqualTo(specs[0].spec) + assertThat(tile0.tile).isInstanceOf(FakeQSTile::class.java) + assertThat(tile0.tile.isAvailable).isTrue() + + // Tile e is not available and is not in the list + + // Custom Tile + val tile1 = tiles!![1] + assertThat(tile1.spec).isEqualTo(specs[2]) + assertThat(tile1.tile.tileSpec).isEqualTo(specs[2].spec) + assertThat(tile1.tile).isInstanceOf(CustomTile::class.java) + assertThat(tile1.tile.isAvailable).isTrue() + + // Tile d + val tile2 = tiles!![2] + assertThat(tile2.spec).isEqualTo(specs[3]) + assertThat(tile2.tile.tileSpec).isEqualTo(specs[3].spec) + assertThat(tile2.tile).isInstanceOf(FakeQSTile::class.java) + assertThat(tile2.tile.isAvailable).isTrue() + + // Tile non-existent shouldn't be created. Therefore, only 3 tiles total + assertThat(tiles?.size).isEqualTo(3) + } + + @Test + fun logTileCreated() = + testScope.runTest(USER_INFO_0) { + val specs = + listOf( + TileSpec.create("a"), + CUSTOM_TILE_SPEC, + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + + specs.forEach { verify(logger).logTileCreated(it) } + } + + @Test + fun logTileNotFoundInFactory() = + testScope.runTest(USER_INFO_0) { + val specs = + listOf( + TileSpec.create("non_existing"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + + verify(logger, never()).logTileCreated(any()) + verify(logger).logTileNotFoundInFactory(specs[0]) + } + + @Test + fun tileNotAvailableDestroyed_logged() = + testScope.runTest(USER_INFO_0) { + val specs = + listOf( + TileSpec.create("e"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + + verify(logger, never()).logTileCreated(any()) + verify(logger) + .logTileDestroyed( + specs[0], + QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE + ) + } + + @Test + fun someTilesNotValid_repositorySetToDefinitiveList() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val specs = + listOf( + TileSpec.create("a"), + TileSpec.create("e"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + assertThat(tiles).isEqualTo(listOf(TileSpec.create("a"))) + } + + @Test + fun deduplicatedTiles() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(TileSpec.create("a"), TileSpec.create("a")) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + assertThat(tiles?.size).isEqualTo(1) + assertThat(tiles!![0].spec).isEqualTo(specs[0]) + } + + @Test + fun tilesChange_platformTileNotRecreated() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = + listOf( + TileSpec.create("a"), + ) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + val originalTileA = tiles!![0].tile + + tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b")) + + assertThat(tiles?.size).isEqualTo(2) + assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA) + } + + @Test + fun tileRemovedIsDestroyed() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(TileSpec.create("a"), TileSpec.create("c")) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + val originalTileC = tiles!![1].tile + + tileSpecRepository.removeTiles(USER_INFO_0.id, listOf(TileSpec.create("c"))) + + assertThat(tiles?.size).isEqualTo(1) + assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("a")) + + assertThat((originalTileC as FakeQSTile).destroyed).isTrue() + verify(logger) + .logTileDestroyed( + TileSpec.create("c"), + QSPipelineLogger.TileDestroyedReason.TILE_REMOVED + ) + } + + @Test + fun tileBecomesNotAvailable_destroyed() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + val repoTiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val specs = listOf(TileSpec.create("a")) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + val originalTileA = tiles!![0].tile + + // Tile becomes unavailable + (originalTileA as FakeQSTile).available = false + unavailableTiles.add("a") + // and there is some change in the specs + tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b")) + runCurrent() + + assertThat(originalTileA.destroyed).isTrue() + verify(logger) + .logTileDestroyed( + TileSpec.create("a"), + QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE + ) + + assertThat(tiles?.size).isEqualTo(1) + assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("b")) + assertThat(tiles!![0].tile).isNotSameInstanceAs(originalTileA) + + assertThat(repoTiles).isEqualTo(tiles!!.map(TileModel::spec)) + } + + @Test + fun userChange_tilesChange() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs0 = listOf(TileSpec.create("a")) + val specs1 = listOf(TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs0) + tileSpecRepository.setTiles(USER_INFO_1.id, specs1) + + switchUser(USER_INFO_1) + + assertThat(tiles!![0].spec).isEqualTo(specs1[0]) + assertThat(tiles!![0].tile.tileSpec).isEqualTo(specs1[0].spec) + } + + @Test + fun tileNotPresentInSecondaryUser_destroyedInUserChange() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs0 = listOf(TileSpec.create("a")) + val specs1 = listOf(TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs0) + tileSpecRepository.setTiles(USER_INFO_1.id, specs1) + + val originalTileA = tiles!![0].tile + + switchUser(USER_INFO_1) + runCurrent() + + assertThat((originalTileA as FakeQSTile).destroyed).isTrue() + verify(logger) + .logTileDestroyed( + specs0[0], + QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER + ) + } + + @Test + fun userChange_customTileDestroyed_lifecycleNotTerminated() { + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(CUSTOM_TILE_SPEC) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + tileSpecRepository.setTiles(USER_INFO_1.id, specs) + + val originalCustomTile = tiles!![0].tile + + switchUser(USER_INFO_1) + runCurrent() + + verify(originalCustomTile).destroy() + assertThat(tileLifecycleManagerFactory.created).isEmpty() + } + } + + @Test + fun userChange_sameTileUserChanged() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(TileSpec.create("a")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + tileSpecRepository.setTiles(USER_INFO_1.id, specs) + + val originalTileA = tiles!![0].tile as FakeQSTile + assertThat(originalTileA.user).isEqualTo(USER_INFO_0.id) + + switchUser(USER_INFO_1) + runCurrent() + + assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA) + assertThat(originalTileA.user).isEqualTo(USER_INFO_1.id) + verify(logger).logTileUserChanged(specs[0], USER_INFO_1.id) + } + + @Test + fun addTile() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + val spec = TileSpec.create("a") + val currentSpecs = + listOf( + TileSpec.create("b"), + TileSpec.create("c"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + + underTest.addTile(spec, position = 1) + + val expectedSpecs = + listOf( + TileSpec.create("b"), + spec, + TileSpec.create("c"), + ) + assertThat(tiles).isEqualTo(expectedSpecs) + } + + @Test + fun addTile_currentUser() = + testScope.runTest(USER_INFO_1) { + val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id)) + val spec = TileSpec.create("a") + val currentSpecs = + listOf( + TileSpec.create("b"), + TileSpec.create("c"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs) + + switchUser(USER_INFO_1) + underTest.addTile(spec, position = 1) + + assertThat(tiles0).isEqualTo(currentSpecs) + + val expectedSpecs = + listOf( + TileSpec.create("b"), + spec, + TileSpec.create("c"), + ) + assertThat(tiles1).isEqualTo(expectedSpecs) + } + + @Test + fun removeTile_platform() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val specs = listOf(TileSpec.create("a"), TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + + underTest.removeTiles(specs.subList(0, 1)) + + assertThat(tiles).isEqualTo(specs.subList(1, 2)) + } + + @Test + fun removeTile_customTile_lifecycleEnded() { + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) + .isTrue() + + underTest.removeTiles(listOf(CUSTOM_TILE_SPEC)) + + assertThat(tiles).isEqualTo(specs.subList(0, 1)) + + val tileLifecycleManager = + tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT] + assertThat(tileLifecycleManager).isNotNull() + + with(inOrder(tileLifecycleManager!!)) { + verify(tileLifecycleManager).onStopListening() + verify(tileLifecycleManager).onTileRemoved() + verify(tileLifecycleManager).flushMessagesAndUnbind() + } + assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) + .isFalse() + verify(customTileStatePersister) + .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id)) + } + } + + @Test + fun removeTiles_currentUser() = + testScope.runTest { + val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id)) + val currentSpecs = + listOf( + TileSpec.create("a"), + TileSpec.create("b"), + TileSpec.create("c"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs) + + switchUser(USER_INFO_1) + runCurrent() + + underTest.removeTiles(currentSpecs.subList(0, 2)) + + assertThat(tiles0).isEqualTo(currentSpecs) + assertThat(tiles1).isEqualTo(currentSpecs.subList(2, 3)) + } + + @Test + fun setTiles() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val currentSpecs = listOf(TileSpec.create("a"), TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + runCurrent() + + val newSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"), TileSpec.create("a")) + underTest.setTiles(newSpecs) + runCurrent() + + assertThat(tiles).isEqualTo(newSpecs) + } + + @Test + fun setTiles_customTiles_lifecycleEndedIfGone() = + testScope.runTest(USER_INFO_0) { + val otherCustomTileSpec = TileSpec.create("custom(b/c)") + + val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a"), otherCustomTileSpec) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + runCurrent() + + val newSpecs = + listOf( + otherCustomTileSpec, + TileSpec.create("a"), + ) + + underTest.setTiles(newSpecs) + runCurrent() + + val tileLifecycleManager = + tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]!! + + with(inOrder(tileLifecycleManager)) { + verify(tileLifecycleManager).onStopListening() + verify(tileLifecycleManager).onTileRemoved() + verify(tileLifecycleManager).flushMessagesAndUnbind() + } + assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) + .isFalse() + verify(customTileStatePersister) + .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id)) + } + + @Test + fun protoDump() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + val stateA = tiles!![0].tile.state + stateA.fillIn(Tile.STATE_INACTIVE, "A", "AA") + val stateCustom = QSTile.BooleanState() + stateCustom.fillIn(Tile.STATE_ACTIVE, "B", "BB") + stateCustom.spec = CUSTOM_TILE_SPEC.spec + whenever(tiles!![1].tile.state).thenReturn(stateCustom) + + val proto = SystemUIProtoDump() + underTest.dumpProto(proto, emptyArray()) + + assertThat(MessageNano.messageNanoEquals(proto.tiles[0], stateA.toProto())).isTrue() + assertThat(MessageNano.messageNanoEquals(proto.tiles[1], stateCustom.toProto())) + .isTrue() + } + + private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) { + this.state = state + this.label = label + this.secondaryLabel = secondaryLabel + if (this is BooleanState) { + value = state == Tile.STATE_ACTIVE + } + } + + private fun tileCreator(spec: String): QSTile? { + val currentUser = userTracker.userId + return when (spec) { + CUSTOM_TILE_SPEC.spec -> + mock<CustomTile> { + var tileSpecReference: String? = null + whenever(user).thenReturn(currentUser) + whenever(component).thenReturn(CUSTOM_TILE_SPEC.componentName) + whenever(isAvailable).thenReturn(true) + whenever(setTileSpec(anyString())).thenAnswer { + tileSpecReference = it.arguments[0] as? String + Unit + } + whenever(tileSpec).thenAnswer { tileSpecReference } + // Also, add it to the set of added tiles (as this happens as part of the tile + // creation). + customTileAddedRepository.setTileAdded( + CUSTOM_TILE_SPEC.componentName, + currentUser, + true + ) + } + in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles) + else -> null + } + } + + private fun TestScope.runTest(user: UserInfo, body: suspend TestScope.() -> Unit) { + return runTest { + switchUser(user) + body() + } + } + + private suspend fun switchUser(user: UserInfo) { + setUserTracker(user.id) + userRepository.setSelectedUserInfo(user) + } + + private fun setUserTracker(user: Int) { + val mockContext = mockUserContext(user) + whenever(userTracker.userContext).thenReturn(mockContext) + whenever(userTracker.userId).thenReturn(user) + } + + private class TLMFactory : TileLifecycleManager.Factory { + + val created = mutableMapOf<Pair<Int, ComponentName>, TileLifecycleManager>() + + override fun create(intent: Intent, userHandle: UserHandle): TileLifecycleManager { + val componentName = intent.component!! + val user = userHandle.identifier + val manager: TileLifecycleManager = mock() + created[user to componentName] = manager + return manager + } + } + + private fun mockUserContext(user: Int): Context { + return mock { + whenever(this.userId).thenReturn(user) + whenever(this.user).thenReturn(UserHandle.of(user)) + } + } + + companion object { + private val USER_INFO_0 = UserInfo().apply { id = 0 } + private val USER_INFO_1 = UserInfo().apply { id = 1 } + + private val VALID_TILES = setOf("a", "b", "c", "d", "e") + private val TEST_COMPONENT = ComponentName("pkg", "cls") + private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt new file mode 100644 index 000000000000..e50969641a71 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt @@ -0,0 +1,95 @@ +/* + * 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.qs.pipeline.domain.interactor + +import android.content.Context +import android.view.View +import com.android.internal.logging.InstanceId +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile + +class FakeQSTile( + var user: Int, + var available: Boolean = true, +) : QSTile { + private var tileSpec: String? = null + var destroyed = false + private val state = QSTile.State() + + override fun getTileSpec(): String? { + return tileSpec + } + + override fun isAvailable(): Boolean { + return available + } + + override fun setTileSpec(tileSpec: String?) { + this.tileSpec = tileSpec + state.spec = tileSpec + } + + override fun refreshState() {} + + override fun addCallback(callback: QSTile.Callback?) {} + + override fun removeCallback(callback: QSTile.Callback?) {} + + override fun removeCallbacks() {} + + override fun createTileView(context: Context?): QSIconView? { + return null + } + + override fun click(view: View?) {} + + override fun secondaryClick(view: View?) {} + + override fun longClick(view: View?) {} + + override fun userSwitch(currentUser: Int) { + user = currentUser + } + + override fun getMetricsCategory(): Int { + return 0 + } + + override fun setListening(client: Any?, listening: Boolean) {} + + override fun setDetailListening(show: Boolean) {} + + override fun destroy() { + destroyed = true + } + + override fun getTileLabel(): CharSequence { + return "" + } + + override fun getState(): QSTile.State { + return state + } + + override fun getInstanceId(): InstanceId { + return InstanceId.fakeInstanceId(0) + } + + override fun isListening(): Boolean { + return false + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 36549fb826ec..962b53737274 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -61,6 +61,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.systemui.InstanceIdSequenceFake; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; @@ -69,6 +70,8 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSEvent; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; +import com.android.systemui.qs.QsEventLoggerFake; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.StatusBarState; @@ -106,7 +109,8 @@ public class QSTileImplTest extends SysuiTestCase { private ActivityStarter mActivityStarter; private UiEventLoggerFake mUiEventLoggerFake; - private InstanceId mInstanceId = InstanceId.fakeInstanceId(5); + private QsEventLoggerFake mQsEventLoggerFake; + private InstanceId mInstanceId; @Captor private ArgumentCaptor<LogMaker> mLogCaptor; @@ -115,18 +119,29 @@ public class QSTileImplTest extends SysuiTestCase { public void setup() throws Exception { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); + mUiEventLoggerFake = new UiEventLoggerFake(); + mQsEventLoggerFake = + new QsEventLoggerFake(mUiEventLoggerFake, new InstanceIdSequenceFake(10)); when(mHost.indexOf(SPEC)).thenReturn(POSITION); when(mHost.getContext()).thenReturn(mContext); - when(mHost.getUiEventLogger()).thenReturn(mUiEventLoggerFake); - when(mHost.getNewInstanceId()).thenReturn(mInstanceId); Handler mainHandler = new Handler(mTestableLooper.getLooper()); - mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager, - mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger); + mTile = new TileImpl( + mHost, + mQsEventLoggerFake, + mTestableLooper.getLooper(), + mainHandler, + mFalsingManager, + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQsLogger + ); mTile.initialize(); mTestableLooper.processAllMessages(); + mInstanceId = InstanceId.fakeInstanceId(mQsEventLoggerFake.getLastInstanceId()); mTile.setTileSpec(SPEC); } @@ -507,6 +522,7 @@ public class QSTileImplTest extends SysuiTestCase { protected TileImpl( QSHost host, + QsEventLogger uiEventLogger, Looper backgroundLooper, Handler mainHandler, FalsingManager falsingManager, @@ -515,7 +531,7 @@ public class QSTileImplTest extends SysuiTestCase { ActivityStarter activityStarter, QSLogger qsLogger ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); getState().state = Tile.STATE_ACTIVE; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt index 5e0190b65a12..c60cecb29d75 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt @@ -22,8 +22,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -32,6 +30,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.settings.UserTracker @@ -68,20 +67,21 @@ class AirplaneModeTileTest : SysuiTestCase() { private lateinit var mGlobalSettings: GlobalSettings @Mock private lateinit var mUserTracker: UserTracker + @Mock + private lateinit var mUiEventLogger: QsEventLogger private lateinit var mTestableLooper: TestableLooper private lateinit var mTile: AirplaneModeTile - private val mUiEventLogger: UiEventLogger = UiEventLoggerFake() - @Before fun setUp() { MockitoAnnotations.initMocks(this) mTestableLooper = TestableLooper.get(this) Mockito.`when`(mHost.context).thenReturn(mContext) - Mockito.`when`(mHost.uiEventLogger).thenReturn(mUiEventLogger) Mockito.`when`(mHost.userContext).thenReturn(mContext) - mTile = AirplaneModeTile(mHost, + mTile = AirplaneModeTile( + mHost, + mUiEventLogger, mTestableLooper.looper, Handler(mTestableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt index f1e3e8a71398..52b84559f396 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt @@ -9,12 +9,12 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.NextAlarmController @@ -28,8 +28,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -52,7 +52,7 @@ class AlarmTileTest : SysuiTestCase() { @Mock private lateinit var nextAlarmController: NextAlarmController @Mock - private lateinit var uiEventLogger: UiEventLogger + private lateinit var uiEventLogger: QsEventLogger @Mock private lateinit var pendingIntent: PendingIntent @Captor @@ -67,10 +67,10 @@ class AlarmTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) `when`(qsHost.context).thenReturn(mContext) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) tile = AlarmTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt index a5c0004afe02..ff6814c06001 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.BatteryController @@ -43,10 +44,10 @@ 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.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -63,6 +64,8 @@ class BatterySaverTileTest : SysuiTestCase() { @Mock private lateinit var qsHost: QSHost @Mock + private lateinit var uiEventLogger: QsEventLogger + @Mock private lateinit var metricsLogger: MetricsLogger @Mock private lateinit var statusBarStateController: StatusBarStateController @@ -90,6 +93,7 @@ class BatterySaverTileTest : SysuiTestCase() { tile = BatterySaverTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 2e77de270c65..5e7f68ccf3d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -9,7 +9,6 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.settingslib.Utils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.systemui.R @@ -20,6 +19,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.BluetoothController @@ -49,8 +49,8 @@ class BluetoothTileTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var bluetoothController: BluetoothController + @Mock private lateinit var uiEventLogger: QsEventLogger - private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: FakeBluetoothTile @@ -60,11 +60,11 @@ class BluetoothTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) whenever(qsHost.context).thenReturn(mContext) - whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) tile = FakeBluetoothTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, @@ -211,6 +211,7 @@ class BluetoothTileTest : SysuiTestCase() { private class FakeBluetoothTile( qsHost: QSHost, + uiEventLogger: QsEventLogger, backgroundLooper: Looper, mainHandler: Handler, falsingManager: FalsingManager, @@ -222,6 +223,7 @@ class BluetoothTileTest : SysuiTestCase() { ) : BluetoothTile( qsHost, + uiEventLogger, backgroundLooper, mainHandler, falsingManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt index 41938541124a..70d82fdb6e5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt @@ -21,8 +21,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -30,6 +28,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLoggerFake import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController @@ -67,19 +66,21 @@ class CameraToggleTileTest : SysuiTestCase() { private lateinit var privacyController: IndividualSensorPrivacyController @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var uiEventLogger: QsEventLoggerFake private lateinit var testableLooper: TestableLooper private lateinit var tile: CameraToggleTile - private val uiEventLogger: UiEventLogger = UiEventLoggerFake() @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) whenever(host.context).thenReturn(mContext) - whenever(host.uiEventLogger).thenReturn(uiEventLogger) - tile = CameraToggleTile(host, + tile = CameraToggleTile( + host, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), metricsLogger, 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 64fd09d5f5d9..93ed99423f0f 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 @@ -43,6 +43,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.NetworkController; @@ -94,6 +95,8 @@ public class CastTileTest extends SysuiTestCase { private QSLogger mQSLogger; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private CastTile mCastTile; @@ -107,6 +110,7 @@ public class CastTileTest extends SysuiTestCase { mCastTile = new CastTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java index 13c30e9ea9ab..2250ef33f9b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java @@ -32,12 +32,12 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.FakeSettings; @@ -67,7 +67,7 @@ public class ColorCorrectionTileTest extends SysuiTestCase { @Mock private QSLogger mQSLogger; @Mock - private UiEventLogger mUiEventLogger; + private QsEventLogger mUiEventLogger; @Mock private UserTracker mUserTracker; @@ -83,10 +83,10 @@ public class ColorCorrectionTileTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); when(mHost.getContext()).thenReturn(mContext); - when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger); mTile = new ColorCorrectionTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java index ff27e0255aa3..2e02bbe2ebf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java @@ -32,7 +32,6 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; @@ -40,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; @@ -72,7 +72,7 @@ public class ColorInversionTileTest extends SysuiTestCase { @Mock private QSLogger mQSLogger; @Mock - private UiEventLogger mUiEventLogger; + private QsEventLogger mUiEventLogger; @Mock private UserTracker mUserTracker; @@ -88,10 +88,10 @@ public class ColorInversionTileTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); when(mHost.getContext()).thenReturn(mContext); - when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger); mTile = new ColorInversionTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt index b048643aba84..176b33fa9fc8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -21,7 +21,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator @@ -30,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.DataSaverController @@ -57,8 +57,8 @@ class DataSaverTileTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var dataSaverController: DataSaverController @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Mock private lateinit var uiEventLogger: QsEventLogger - private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: DataSaverTile @@ -68,11 +68,11 @@ class DataSaverTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) Mockito.`when`(mHost.context).thenReturn(mContext) - Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger) tile = DataSaverTile( mHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index b51c378f6b6b..1346069d3c25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -27,7 +27,6 @@ import android.testing.TestableLooper import androidx.lifecycle.LifecycleOwner import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator @@ -44,6 +43,7 @@ import com.android.systemui.controls.ui.SelectedItem import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.util.mockito.any @@ -52,6 +52,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -59,15 +60,14 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Captor import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.Mockito.`when` import org.mockito.Mockito.doNothing import org.mockito.Mockito.nullable import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations import java.util.Optional -import org.junit.After @SmallTest @RunWith(AndroidTestingRunner::class) @@ -95,7 +95,7 @@ class DeviceControlsTileTest : SysuiTestCase() { @Mock private lateinit var serviceInfo: ControlsServiceInfo @Mock - private lateinit var uiEventLogger: UiEventLogger + private lateinit var uiEventLogger: QsEventLogger @Captor private lateinit var listingCallbackCaptor: ArgumentCaptor<ControlsListingController.ControlsListingCallback> @@ -118,7 +118,6 @@ class DeviceControlsTileTest : SysuiTestCase() { spiedContext = spy(mContext) doNothing().`when`(spiedContext).startActivity(any(Intent::class.java)) `when`(qsHost.context).thenReturn(spiedContext) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) `when`(controlsComponent.isEnabled()).thenReturn(true) `when`(controlsController.getPreferredSelection()) .thenReturn(SelectedItem.StructureItem( @@ -399,6 +398,7 @@ class DeviceControlsTileTest : SysuiTestCase() { private fun createTile(): DeviceControlsTile { return DeviceControlsTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index 6c0904eb9bfd..f0e4e3adda7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -28,7 +28,6 @@ import android.testing.TestableLooper import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator @@ -37,6 +36,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.ZenModeController @@ -46,7 +46,6 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat -import java.io.File import org.junit.After import org.junit.Before import org.junit.Test @@ -55,8 +54,9 @@ import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import java.io.File +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -84,7 +84,7 @@ class DndTileTest : SysuiTestCase() { private lateinit var qsLogger: QSLogger @Mock - private lateinit var uiEventLogger: UiEventLogger + private lateinit var uiEventLogger: QsEventLogger @Mock private lateinit var zenModeController: ZenModeController @@ -109,7 +109,6 @@ class DndTileTest : SysuiTestCase() { secureSettings = FakeSettings() whenever(qsHost.userId).thenReturn(DEFAULT_USER) - whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) val wrappedContext = object : ContextWrapper(context) { override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { @@ -120,6 +119,7 @@ class DndTileTest : SysuiTestCase() { tile = DndTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java index 7d41aa6c3548..f231c6e56096 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java @@ -48,6 +48,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; @@ -83,6 +84,8 @@ public class DreamTileTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private UserTracker mUserTracker; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; @@ -258,6 +261,7 @@ public class DreamTileTest extends SysuiTestCase { boolean dreamOnlyEnabledForSystemUser) { return new DreamTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt index 692a64422a7d..73aa6991c18a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt @@ -6,7 +6,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -14,6 +13,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.FlashlightController @@ -45,7 +45,8 @@ class FlashlightTileTest : SysuiTestCase() { @Mock private lateinit var flashlightController: FlashlightController - private val uiEventLogger = UiEventLoggerFake() + @Mock private lateinit var uiEventLogger: QsEventLogger + private val falsingManager = FalsingManagerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: FlashlightTile @@ -56,11 +57,11 @@ class FlashlightTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) Mockito.`when`(qsHost.context).thenReturn(mockContext) - Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) tile = FlashlightTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt index eeebd4fb7792..1d6f225dd0a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt @@ -23,7 +23,6 @@ import android.testing.TestableLooper import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.classifier.FalsingManagerFake @@ -31,7 +30,8 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.qs.QSTileHost +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -52,13 +52,13 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class FontScalingTileTest : SysuiTestCase() { - @Mock private lateinit var qsHost: QSTileHost + @Mock private lateinit var qsHost: QSHost @Mock private lateinit var metricsLogger: MetricsLogger @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator - @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var uiEventLogger: QsEventLogger private lateinit var testableLooper: TestableLooper private lateinit var fontScalingTile: FontScalingTile @@ -70,11 +70,11 @@ class FontScalingTileTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) `when`(qsHost.getContext()).thenReturn(mContext) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) fontScalingTile = FontScalingTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java index 959e750ac5f4..73f61d0690e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.DataSaverController; @@ -66,6 +67,8 @@ public class HotspotTileTest extends SysuiTestCase { private HotspotController mHotspotController; @Mock private DataSaverController mDataSaverController; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private HotspotTile mTile; @@ -80,6 +83,7 @@ public class HotspotTileTest extends SysuiTestCase { mTile = new HotspotTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java index adfd7f71e8f8..7957c6a7cfb6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java @@ -35,6 +35,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; @@ -63,6 +64,8 @@ public class InternetTileTest extends SysuiTestCase { private AccessPointController mAccessPointController; @Mock private InternetDialogFactory mInternetDialogFactory; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private InternetTile mTile; @@ -76,6 +79,7 @@ public class InternetTileTest extends SysuiTestCase { mTile = new InternetTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt index 3642e874e7ae..0bf0b38f7471 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt @@ -22,7 +22,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -30,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.qs.tileimpl.QSTileImpl @@ -43,8 +43,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -71,8 +71,9 @@ class LocationTileTest : SysuiTestCase() { private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var panelInteractor: PanelInteractor + @Mock + private lateinit var uiEventLogger: QsEventLogger - private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: LocationTile @@ -80,10 +81,11 @@ class LocationTileTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) `when`(qsHost.context).thenReturn(mockContext) - tile = LocationTile(qsHost, + tile = LocationTile( + qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt index e2f64b2cc226..ceff546106f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt @@ -21,8 +21,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -30,6 +28,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController @@ -67,19 +66,22 @@ class MicrophoneToggleTileTest : SysuiTestCase() { private lateinit var privacyController: IndividualSensorPrivacyController @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var uiEventLogger: QsEventLogger private lateinit var testableLooper: TestableLooper private lateinit var tile: MicrophoneToggleTile - private val uiEventLogger: UiEventLogger = UiEventLoggerFake() + @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) whenever(host.context).thenReturn(mContext) - whenever(host.uiEventLogger).thenReturn(uiEventLogger) - tile = MicrophoneToggleTile(host, + tile = MicrophoneToggleTile( + host, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), metricsLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java index c7dae83e2056..763a7e5e4e06 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java @@ -37,6 +37,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import org.junit.After; @@ -70,6 +71,8 @@ public class NfcTileTest extends SysuiTestCase { private QSLogger mQSLogger; @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private NfcTile mNfcTile; @@ -84,6 +87,7 @@ public class NfcTileTest extends SysuiTestCase { mNfcTile = new NfcTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt index 04af69c84cf8..6c8f76b48f03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt @@ -23,8 +23,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -33,6 +31,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.LocationController @@ -43,8 +42,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.anyInt -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -68,17 +67,18 @@ class NightDisplayTileTest : SysuiTestCase() { @Mock private lateinit var mNightDisplayListener: NightDisplayListener + @Mock private lateinit var mUiEventLogger: QsEventLogger + private lateinit var mTestableLooper: TestableLooper private lateinit var mTile: NightDisplayTile - private val mUiEventLogger: UiEventLogger = UiEventLoggerFake() + @Before fun setUp() { MockitoAnnotations.initMocks(this) mTestableLooper = TestableLooper.get(this) whenever(mHost.context).thenReturn(mContext) - whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger) whenever(mHost.userContext).thenReturn(mContext) whenever(mNightDisplayListenerBuilder.setUser(anyInt())) .thenReturn(mNightDisplayListenerBuilder) @@ -87,6 +87,7 @@ class NightDisplayTileTest : SysuiTestCase() { mTile = NightDisplayTile( mHost, + mUiEventLogger, mTestableLooper.looper, Handler(mTestableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java index 652c138f6478..c391153fecc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java @@ -33,6 +33,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -65,6 +66,8 @@ public class OneHandedModeTileTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private SecureSettings mSecureSettings; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private OneHandedModeTile mTile; @@ -78,6 +81,7 @@ public class OneHandedModeTileTest extends SysuiTestCase { mTile = spy(new OneHandedModeTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java index 3125d455acfb..6f2d904dda64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java @@ -30,8 +30,6 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; @@ -40,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -64,7 +63,8 @@ public class QRCodeScannerTileTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private QSLogger mQSLogger; - private UiEventLogger mUiEventLogger = new UiEventLoggerFake(); + @Mock + private QsEventLogger mUiEventLogger; @Mock private QRCodeScannerController mController; @@ -79,6 +79,7 @@ public class QRCodeScannerTileTest extends SysuiTestCase { mTile = new QRCodeScannerTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index 596df7856ee1..b089e380304d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -58,8 +58,6 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; @@ -67,6 +65,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -109,7 +108,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private QSLogger mQSLogger; - private UiEventLogger mUiEventLogger = new UiEventLoggerFake(); + @Mock + private QsEventLogger mUiEventLogger; @Mock private QuickAccessWalletClient mQuickAccessWalletClient; @Mock @@ -136,7 +136,6 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { doNothing().when(mSpiedContext).startActivity(any(Intent.class)); when(mHost.getContext()).thenReturn(mSpiedContext); - when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger); when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL); when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon); when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); @@ -146,6 +145,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mTile = new QuickAccessWalletTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 7913628c5693..d2445944c182 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -69,6 +70,8 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private ReduceBrightColorsController mReduceBrightColorsController; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private ReduceBrightColorsTile mTile; @@ -85,6 +88,7 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { true, mReduceBrightColorsController, mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java index 5b94cfedaedf..e106741499ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BatteryController; @@ -87,6 +88,8 @@ public class RotationLockTileTest extends SysuiTestCase { DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; @Mock RotationPolicyWrapper mRotationPolicyWrapper; + @Mock + QsEventLogger mUiEventLogger; private RotationLockController mController; private TestableLooper mTestableLooper; @@ -105,6 +108,7 @@ public class RotationLockTileTest extends SysuiTestCase { mLockTile = new RotationLockTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index d9ed1a299f51..fff2b8f5f8cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -44,6 +44,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -86,6 +87,8 @@ public class ScreenRecordTileTest extends SysuiTestCase { private DialogLaunchAnimator mDialogLaunchAnimator; @Mock private PanelInteractor mPanelInteractor; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private ScreenRecordTile mTile; @@ -100,6 +103,7 @@ public class ScreenRecordTileTest extends SysuiTestCase { mTile = new ScreenRecordTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt index b55657163382..79147e7e66b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt @@ -25,7 +25,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -33,6 +32,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.BatteryController @@ -63,8 +63,8 @@ class UiModeNightTileTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var batteryController: BatteryController @Mock private lateinit var locationController: LocationController + @Mock private lateinit var uiEventLogger: QsEventLogger - private val uiEventLogger = UiEventLoggerFake() private val falsingManager = FalsingManagerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: UiModeNightTile @@ -81,11 +81,11 @@ class UiModeNightTileTest : SysuiTestCase() { `when`(qsHost.userContext).thenReturn(mContext) `when`(mockContext.resources).thenReturn(resources) `when`(resources.configuration).thenReturn(configuration) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) tile = UiModeNightTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, 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 99979976a122..5ca37716cbff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -66,6 +67,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardClockSwitch; import com.android.keyguard.KeyguardClockSwitchController; +import com.android.keyguard.KeyguardSliceViewController; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; @@ -74,6 +76,7 @@ import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; +import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; @@ -233,7 +236,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; - @Mock protected KeyguardStatusViewController mKeyguardStatusViewController; @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController; @Mock protected NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @@ -293,6 +295,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock protected MotionEvent mDownMotionEvent; @Mock protected CoroutineDispatcher mMainDispatcher; + @Mock protected KeyguardSliceViewController mKeyguardSliceViewController; + @Mock protected KeyguardLogger mKeyguardLogger; + @Mock protected KeyguardStatusView mKeyguardStatusView; @Captor protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> mEmptySpaceClickListenerCaptor; @@ -309,6 +314,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; protected Handler mMainHandler; protected View.OnLayoutChangeListener mLayoutChangeListener; + protected KeyguardStatusViewController mKeyguardStatusViewController; protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake(); protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); @@ -335,6 +341,18 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); + mKeyguardStatusViewController = spy(new KeyguardStatusViewController( + mKeyguardStatusView, + mKeyguardSliceViewController, + mKeyguardClockSwitchController, + mKeyguardStateController, + mUpdateMonitor, + mConfigurationController, + mDozeParameters, + mScreenOffAnimationController, + mKeyguardLogger, + mFeatureFlags, + mInteractionJankMonitor)); when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); when(mHeadsUpCallback.getContext()).thenReturn(mContext); @@ -366,12 +384,15 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea); when(mKeyguardBottomArea.animate()).thenReturn(mViewPropertyAnimator); when(mView.animate()).thenReturn(mViewPropertyAnimator); + when(mKeyguardStatusView.animate()).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator); + when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setInterpolator(any())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator); + when(mViewPropertyAnimator.withEndAction(any())).thenReturn(mViewPropertyAnimator); when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); @@ -650,9 +671,13 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @After public void tearDown() { - mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); - mNotificationPanelViewController.cancelHeightAnimator(); - mMainHandler.removeCallbacksAndMessages(null); + if (mNotificationPanelViewController != null) { + mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); + mNotificationPanelViewController.cancelHeightAnimator(); + } + if (mMainHandler != null) { + mMainHandler.removeCallbacksAndMessages(null); + } } protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 569f90b64609..4438b98f6fad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -614,9 +614,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricHelp_coEx_faceFailure() { createController(); - // GIVEN unlocking with fingerprint is possible - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt())) - .thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -641,9 +640,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricHelp_coEx_faceUnavailable() { createController(); - // GIVEN unlocking with fingerprint is possible - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt())) - .thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -664,6 +662,35 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mContext.getString(R.string.keyguard_suggest_fingerprint)); } + + @Test + public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() { + createController(); + + // GIVEN unlocking with fingerprint is possible but not allowed + setupFingerprintUnlockPossible(true); + when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()) + .thenReturn(false); + + String message = "A message"; + mController.setVisible(true); + + // WHEN there's a face unavailable message + mController.getKeyguardCallback().onBiometricHelp( + BIOMETRIC_HELP_FACE_NOT_AVAILABLE, + message, + BiometricSourceType.FACE); + + // THEN show sequential messages such as: 'face unlock unavailable' and + // 'try fingerprint instead' + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE, + message); + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_unlock)); + } + @Test public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() { createController(); @@ -818,8 +845,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() { createController(); - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -832,9 +858,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendFaceHelpMessages_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help messages received that are allowed to show final String helpString = "helpString"; @@ -859,9 +884,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help messages received that aren't supposed to show final String helpString = "helpString"; @@ -886,9 +910,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendAllFaceHelpMessages_fingerprintNotEnrolled() { createController(); - // GIVEN fingerprint NOT enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(false); + // GIVEN fingerprint NOT possible + fingerprintUnlockIsNotPossible(); // WHEN help messages received final Set<CharSequence> helpStrings = new HashSet<>(); @@ -917,9 +940,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() { createController(); - // GIVEN fingerprint NOT enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(false); + // GIVEN fingerprint not possible + fingerprintUnlockIsNotPossible(); // WHEN help message received and deferred message is valid final String helpString = "helpMsg"; @@ -948,9 +970,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help message received and deferredMessage is valid final String helpString = "helpMsg"; @@ -1500,7 +1521,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() { createController(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("first lockout"); verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, @@ -1559,7 +1580,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() { createController(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("first lockout"); clearInvocations(mRotateTextViewController); @@ -1668,7 +1689,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() { createController(); screenIsTurningOn(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("lockout error"); verifyNoMoreInteractions(mRotateTextViewController); @@ -1746,8 +1767,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { setupFingerprintUnlockPossible(false); } - private void fingerprintUnlockIsPossible() { + private void fingerprintUnlockIsPossibleAndAllowed() { setupFingerprintUnlockPossible(true); + when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true); } private void setupFingerprintUnlockPossible(boolean possible) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 7b59cc284181..08a9f3139d71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout -import androidx.core.animation.AnimatorTestRule2 +import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -70,7 +70,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler private val fakeFeatureFlags = FakeFeatureFlags() - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index be3b7234a1a2..aff705f1f3bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import androidx.core.animation.AnimatorTestRule2 +import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -51,7 +51,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @TestableLooper.RunWithLooper(setAsMainLooper = true) class NotificationWakeUpCoordinatorTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() private val dumpManager: DumpManager = mock() private val headsUpManager: HeadsUpManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java index 30708a7cb2fe..ac66ad9e9c8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -97,6 +97,59 @@ public class HighPriorityProviderTest extends SysuiTestCase { } @Test + public void highImportanceConversation() { + // GIVEN notification is high importance and is a people notification + final Notification notification = new Notification.Builder(mContext, "test") + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_HIGH) + .build(); + when(mPeopleNotificationIdentifier + .getPeopleNotificationType(entry)) + .thenReturn(TYPE_PERSON); + + // THEN it is high priority conversation + assertTrue(mHighPriorityProvider.isHighPriorityConversation(entry)); + } + + @Test + public void lowImportanceConversation() { + // GIVEN notification is high importance and is a people notification + final Notification notification = new Notification.Builder(mContext, "test") + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mPeopleNotificationIdentifier + .getPeopleNotificationType(entry)) + .thenReturn(TYPE_PERSON); + + // THEN it is low priority conversation + assertFalse(mHighPriorityProvider.isHighPriorityConversation(entry)); + } + + @Test + public void highImportanceConversationWhenAnyOfChildIsHighPriority() { + // GIVEN notification is high importance and is a people notification + final NotificationEntry summary = createNotifEntry(false); + final NotificationEntry lowPriorityChild = createNotifEntry(false); + final NotificationEntry highPriorityChild = createNotifEntry(true); + when(mPeopleNotificationIdentifier + .getPeopleNotificationType(summary)) + .thenReturn(TYPE_PERSON); + final GroupEntry groupEntry = new GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(List.of(lowPriorityChild, highPriorityChild)) + .build(); + + // THEN the groupEntry is high priority conversation since it has a high priority child + assertTrue(mHighPriorityProvider.isHighPriorityConversation(groupEntry)); + } + + @Test public void messagingStyle() { // GIVEN notification is low importance but has messaging style final Notification notification = new Notification.Builder(mContext, "test") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index 742fcf5e03c3..55ea31571dfe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -17,6 +17,9 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.app.NotificationManager.IMPORTANCE_LOW import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -31,10 +34,13 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.icon.ConversationIconManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.withArgCaptor @@ -55,7 +61,8 @@ import org.mockito.Mockito.`when` as whenever class ConversationCoordinatorTest : SysuiTestCase() { // captured listeners and pluggables: private lateinit var promoter: NotifPromoter - private lateinit var peopleSectioner: NotifSectioner + private lateinit var peopleAlertingSectioner: NotifSectioner + private lateinit var peopleSilentSectioner: NotifSectioner private lateinit var peopleComparator: NotifComparator private lateinit var beforeRenderListListener: OnBeforeRenderListListener @@ -76,6 +83,7 @@ class ConversationCoordinatorTest : SysuiTestCase() { coordinator = ConversationCoordinator( peopleNotificationIdentifier, conversationIconManager, + HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()), headerController ) whenever(channel.isImportantConversation).thenReturn(true) @@ -90,12 +98,13 @@ class ConversationCoordinatorTest : SysuiTestCase() { verify(pipeline).addOnBeforeRenderListListener(capture()) } - peopleSectioner = coordinator.sectioner - peopleComparator = peopleSectioner.comparator!! + peopleAlertingSectioner = coordinator.peopleAlertingSectioner + peopleSilentSectioner = coordinator.peopleSilentSectioner + peopleComparator = peopleAlertingSectioner.comparator!! entry = NotificationEntryBuilder().setChannel(channel).build() - val section = NotifSection(peopleSectioner, 0) + val section = NotifSection(peopleAlertingSectioner, 0) entryA = NotificationEntryBuilder().setChannel(channel) .setSection(section).setTag("A").build() entryB = NotificationEntryBuilder().setChannel(channel) @@ -129,13 +138,67 @@ class ConversationCoordinatorTest : SysuiTestCase() { } @Test - fun testInPeopleSection() { + fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() { + // GIVEN + val alertingEntry = NotificationEntryBuilder().setChannel(channel) + .setImportance(IMPORTANCE_DEFAULT).build() + whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry)) + .thenReturn(TYPE_PERSON) + + // put alerting people notifications in this section + assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue() + } + + @Test + fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() { + // GIVEN + val silentEntry = NotificationEntryBuilder().setChannel(channel) + .setImportance(IMPORTANCE_LOW).build() + whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry)) + .thenReturn(TYPE_PERSON) + + // THEN put silent people notifications in this section + assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue() + // People Alerting sectioning happens before the silent one. + // It claims high important conversations and rest of conversations will be considered as silent. + assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse() + } + + @Test + fun testNotInPeopleSection() { + // GIVEN + val entry = NotificationEntryBuilder().setChannel(channel) + .setImportance(IMPORTANCE_LOW).build() + val importantEntry = NotificationEntryBuilder().setChannel(channel) + .setImportance(IMPORTANCE_HIGH).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_NON_PERSON) + whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry)) + .thenReturn(TYPE_NON_PERSON) - // only put people notifications in this section - assertTrue(peopleSectioner.isInSection(entry)) - assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build())) + // THEN - only put people notification either silent or alerting + assertThat(peopleSilentSectioner.isInSection(entry)).isFalse() + assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse() + } + + @Test + fun testInAlertingPeopleSectionWhenThereIsAnImportantChild(){ + // GIVEN + val altChildA = NotificationEntryBuilder().setTag("A") + .setImportance(IMPORTANCE_DEFAULT).build() + val altChildB = NotificationEntryBuilder().setTag("B") + .setImportance(IMPORTANCE_LOW).build() + val summary = NotificationEntryBuilder().setId(2) + .setImportance(IMPORTANCE_LOW).setChannel(channel).build() + val groupEntry = GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(listOf(altChildA, altChildB)) + .build() + whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary)) + .thenReturn(TYPE_PERSON) + // THEN + assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index d5c0c5564af6..3d1253e2b05d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -52,7 +52,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; @@ -73,7 +72,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private SectionStyleProvider mSectionStyleProvider; @Mock private NotifPipeline mNotifPipeline; @Mock private NodeController mAlertingHeaderController; @Mock private NodeController mSilentNodeController; @@ -100,7 +98,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { mRankingCoordinator = new RankingCoordinator( mStatusBarStateController, mHighPriorityProvider, - mSectionStyleProvider, mAlertingHeaderController, mSilentHeaderController, mSilentNodeController); @@ -108,7 +105,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { mEntry.setRanking(getRankingForUnfilteredNotif().build()); mRankingCoordinator.attach(mNotifPipeline); - verify(mSectionStyleProvider).setMinimizedSections(any()); verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture()); mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0); mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1); 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 7d022192f935..9186c35e2b47 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 @@ -100,7 +100,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL); FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags(); - fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true); fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false); mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt new file mode 100644 index 000000000000..2cc375b3d0ed --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt @@ -0,0 +1,88 @@ +/* + * 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.notification.shelf.domain.interactor + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +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 + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class NotificationShelfInteractorTest : SysuiTestCase() { + + private val keyguardRepository = FakeKeyguardRepository() + private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository() + private val underTest = + NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository) + + @Test + fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest { + val shelfStatic by collectLastValue(underTest.isShelfStatic) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(shelfStatic).isFalse() + } + + @Test + fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest { + val shelfStatic by collectLastValue(underTest.isShelfStatic) + + keyguardRepository.setKeyguardShowing(true) + deviceEntryFaceAuthRepository.isBypassEnabled.value = false + + assertThat(shelfStatic).isFalse() + } + + @Test + fun shelfIsStatic_whenBypass() = runTest { + val shelfStatic by collectLastValue(underTest.isShelfStatic) + + keyguardRepository.setKeyguardShowing(true) + deviceEntryFaceAuthRepository.isBypassEnabled.value = true + + assertThat(shelfStatic).isTrue() + } + + @Test + fun shelfOnKeyguard_whenKeyguardShowing() = runTest { + val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard) + + keyguardRepository.setKeyguardShowing(true) + + assertThat(onKeyguard).isTrue() + } + + @Test + fun shelfNotOnKeyguard_whenKeyguardNotShowing() = runTest { + val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(onKeyguard).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt new file mode 100644 index 000000000000..439edaf3faaf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt @@ -0,0 +1,90 @@ +/* + * 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.notification.shelf.ui.viewmodel + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor +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 + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class NotificationShelfViewModelTest : SysuiTestCase() { + + private val keyguardRepository = FakeKeyguardRepository() + private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository() + private val interactor = + NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository) + private val underTest = NotificationShelfViewModel(interactor) + + @Test + fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest { + val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(canModifyNotifColor).isTrue() + } + + @Test + fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest { + val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications) + + keyguardRepository.setKeyguardShowing(true) + deviceEntryFaceAuthRepository.isBypassEnabled.value = false + + assertThat(canModifyNotifColor).isTrue() + } + + @Test + fun cannotModifyColorOfNotifications_whenBypass() = runTest { + val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications) + + keyguardRepository.setKeyguardShowing(true) + deviceEntryFaceAuthRepository.isBypassEnabled.value = true + + assertThat(canModifyNotifColor).isFalse() + } + + @Test + fun isClickable_whenKeyguardShowing() = runTest { + val isClickable by collectLastValue(underTest.isClickable) + + keyguardRepository.setKeyguardShowing(true) + + assertThat(isClickable).isTrue() + } + + @Test + fun isNotClickable_whenKeyguardNotShowing() = runTest { + val isClickable by collectLastValue(underTest.isClickable) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(isClickable).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index e6f10cdafe03..5279740a8702 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -23,6 +23,7 @@ import android.view.View.VISIBLE import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -48,6 +49,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController + @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var stackLayout: NotificationStackScrollLayout private val testableResources = mContext.orCreateTestableResources @@ -67,7 +69,9 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { NotificationStackSizeCalculator( statusBarStateController = sysuiStatusBarStateController, lockscreenShadeTransitionController = lockscreenShadeTransitionController, - testableResources.resources) + mediaDataManager = mediaDataManager, + testableResources.resources + ) } @Test @@ -76,7 +80,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val maxNotifications = computeMaxKeyguardNotifications( - rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f) + rows, + spaceForNotifications = 0f, + spaceForShelf = 0f, + shelfHeight = 0f + ) assertThat(maxNotifications).isEqualTo(0) } @@ -91,7 +99,8 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { rows, spaceForNotifications = Float.MAX_VALUE, spaceForShelf = Float.MAX_VALUE, - shelfHeight) + shelfHeight + ) assertThat(maxNotifications).isEqualTo(numberOfRows) } @@ -111,6 +120,28 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test + fun computeMaxKeyguardNotifications_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f) + + val row = createMockRow(10f, isSticky = true) + whenever(row.getMinHeight(any())).thenReturn(5) + + val maxNotifications = + computeMaxKeyguardNotifications( + listOf(row), + /* spaceForNotifications= */ 5f, + /* spaceForShelf= */ 0f, + /* shelfHeight= */ 0f + ) + + assertThat(maxNotifications).isEqualTo(1) + } + + @Test fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() { setGapHeight(gapHeight) val shelfHeight = shelfHeight + dividerHeight @@ -126,7 +157,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val maxNotifications = computeMaxKeyguardNotifications( - rows, spaceForNotifications + 1, spaceForShelf, shelfHeight) + rows, + spaceForNotifications + 1, + spaceForShelf, + shelfHeight + ) assertThat(maxNotifications).isEqualTo(2) } @@ -136,24 +171,23 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { // Each row in separate section. setGapHeight(gapHeight) - val spaceForNotifications = + val notifSpace = listOf( rowHeight, dividerHeight + gapHeight + rowHeight, ) .sum() - val spaceForShelf = dividerHeight + gapHeight + shelfHeight - val spaceUsed = spaceForNotifications + spaceForShelf + val shelfSpace = dividerHeight + gapHeight + shelfHeight + val spaceUsed = notifSpace + shelfSpace val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight)) val maxNotifications = - computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight) + computeMaxKeyguardNotifications(rows, notifSpace, shelfSpace, shelfHeight) assertThat(maxNotifications).isEqualTo(2) - val height = - sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) + val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) assertThat(height).isEqualTo(spaceUsed) } @@ -170,11 +204,14 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { // test that we only use space required val maxNotifications = computeMaxKeyguardNotifications( - rows, spaceForNotifications + 1, spaceForShelf, shelfHeight) + rows, + spaceForNotifications + 1, + spaceForShelf, + shelfHeight + ) assertThat(maxNotifications).isEqualTo(1) - val height = - sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) + val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) assertThat(height).isEqualTo(spaceUsed) } @@ -200,60 +237,101 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test - fun spaceNeeded_onLockscreen_usesMinHeight() { + fun getSpaceNeeded_onLockscreenEnoughSpaceStickyHun_intrinsicHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 - val expandableView = createMockRow(rowHeight) + val row = createMockRow(10f, isSticky = true) + whenever(row.getMinHeight(any())).thenReturn(5) + + val space = + sizeCalculator.getSpaceNeeded( + row, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true + ) + assertThat(space.whenEnoughSpace).isEqualTo(10f) + } + + @Test + fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + val row = createMockRow(rowHeight) + whenever(row.heightWithoutLockscreenConstraints).thenReturn(10) + whenever(row.getMinHeight(any())).thenReturn(5) + + val space = + sizeCalculator.getSpaceNeeded( + row, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true + ) + assertThat(space.whenEnoughSpace).isEqualTo(5) + } + + @Test + fun getSpaceNeeded_onLockscreenSavingSpaceStickyHun_minHeight() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + val expandableView = createMockRow(10f, isSticky = true) whenever(expandableView.getMinHeight(any())).thenReturn(5) - whenever(expandableView.intrinsicHeight).thenReturn(10) val space = - sizeCalculator.spaceNeeded( + sizeCalculator.getSpaceNeeded( expandableView, visibleIndex = 0, previousView = null, stack = stackLayout, - onLockscreen = true) - assertThat(space).isEqualTo(5) + onLockscreen = true + ) + assertThat(space.whenSavingSpace).isEqualTo(5) } @Test - fun spaceNeeded_fsiHunOnLockscreen_usesIntrinsicHeight() { + fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 - val expandableView = createMockStickyRow(rowHeight) + val expandableView = createMockRow(rowHeight) whenever(expandableView.getMinHeight(any())).thenReturn(5) whenever(expandableView.intrinsicHeight).thenReturn(10) val space = - sizeCalculator.spaceNeeded( - expandableView, - visibleIndex = 0, - previousView = null, - stack = stackLayout, - onLockscreen = true) - assertThat(space).isEqualTo(10) + sizeCalculator.getSpaceNeeded( + expandableView, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true + ) + assertThat(space.whenSavingSpace).isEqualTo(5) } @Test - fun spaceNeeded_notOnLockscreen_usesIntrinsicHeight() { + fun getSpaceNeeded_notOnLockscreen_intrinsicHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 val expandableView = createMockRow(rowHeight) - whenever(expandableView.getMinHeight(any())).thenReturn(5) - whenever(expandableView.intrinsicHeight).thenReturn(10) + whenever(expandableView.getMinHeight(any())).thenReturn(1) val space = - sizeCalculator.spaceNeeded( + sizeCalculator.getSpaceNeeded( expandableView, visibleIndex = 0, previousView = null, stack = stackLayout, - onLockscreen = false) - assertThat(space).isEqualTo(10) + onLockscreen = false + ) + assertThat(space.whenEnoughSpace).isEqualTo(rowHeight) + assertThat(space.whenSavingSpace).isEqualTo(rowHeight) } private fun computeMaxKeyguardNotifications( @@ -264,7 +342,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { ): Int { setupChildren(rows) return sizeCalculator.computeMaxKeyguardNotifications( - stackLayout, spaceForNotifications, spaceForShelf, shelfHeight) + stackLayout, + spaceForNotifications, + spaceForShelf, + shelfHeight + ) } private fun setupChildren(children: List<ExpandableView>) { @@ -280,30 +362,13 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { private fun createMockRow( height: Float = rowHeight, + isSticky: Boolean = false, isRemoved: Boolean = false, - visibility: Int = VISIBLE - ): ExpandableNotificationRow { - val row = mock(ExpandableNotificationRow::class.java) - val entry = mock(NotificationEntry::class.java) - val sbn = mock(StatusBarNotification::class.java) - whenever(entry.sbn).thenReturn(sbn) - whenever(row.entry).thenReturn(entry) - whenever(row.isRemoved).thenReturn(isRemoved) - whenever(row.visibility).thenReturn(visibility) - whenever(row.getMinHeight(any())).thenReturn(height.toInt()) - whenever(row.intrinsicHeight).thenReturn(height.toInt()) - return row - } - - private fun createMockStickyRow( - height: Float = rowHeight, - isRemoved: Boolean = false, - visibility: Int = VISIBLE + visibility: Int = VISIBLE, ): ExpandableNotificationRow { val row = mock(ExpandableNotificationRow::class.java) val entry = mock(NotificationEntry::class.java) - whenever(entry.isStickyAndNotDemoted).thenReturn(true) - + whenever(entry.isStickyAndNotDemoted).thenReturn(isSticky) val sbn = mock(StatusBarNotification::class.java) whenever(entry.sbn).thenReturn(sbn) whenever(row.entry).thenReturn(entry) @@ -311,6 +376,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { whenever(row.visibility).thenReturn(visibility) whenever(row.getMinHeight(any())).thenReturn(height.toInt()) whenever(row.intrinsicHeight).thenReturn(height.toInt()) + whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt()) return row } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 32f0adfa1954..48710a42f616 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -133,6 +133,7 @@ import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeControllerImpl; import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.ShadeLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -221,6 +222,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private NotificationListContainer mNotificationListContainer; @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private NotificationPanelViewController mNotificationPanelViewController; + @Mock private ShadeLogger mShadeLogger; @Mock private NotificationPanelView mNotificationPanelView; @Mock private QuickSettingsController mQuickSettingsController; @Mock private IStatusBarService mBarService; @@ -469,6 +471,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mKeyguardViewMediator, new DisplayMetrics(), mMetricsLogger, + mShadeLogger, mUiBgExecutor, mNotificationMediaManager, mLockscreenUserManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 746c92e485b7..02c459b11d07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -16,12 +16,10 @@ package com.android.systemui.statusbar.phone -import android.animation.Animator import android.os.Handler import android.os.PowerManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper -import android.view.View import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase @@ -39,10 +37,8 @@ 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.anyLong import org.mockito.Mockito.never -import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -111,27 +107,6 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { controller.onStartedWakingUp() } - @Test - fun testAnimClearsEndListener() { - val keyguardView = View(context) - val animator = spy(keyguardView.animate()) - val keyguardSpy = spy(keyguardView) - Mockito.`when`(keyguardSpy.animate()).thenReturn(animator) - val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java) - val endAction = ArgumentCaptor.forClass(Runnable::class.java) - controller.animateInKeyguard(keyguardSpy, Runnable {}) - Mockito.verify(animator).setListener(listener.capture()) - Mockito.verify(animator).withEndAction(endAction.capture()) - - // Verify that the listener is cleared if we cancel it. - listener.value.onAnimationCancel(null) - Mockito.verify(animator).setListener(null) - - // Verify that the listener is also cleared if the end action is triggered. - endAction.value.run() - verify(animator, times(2)).setListener(null) - } - /** * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal * animation to start. If the device is woken up during the screen off, we should *never* do diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index 1b6ab4d7af96..297cb9d691ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -179,15 +179,71 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test - fun iconId_cutout_whenDefaultDataDisabled() = + fun icon_usesLevelFromInteractor() = + testScope.runTest { + var latest: SignalIconModel? = null + val job = underTest.icon.onEach { latest = it }.launchIn(this) + + interactor.level.value = 3 + assertThat(latest!!.level).isEqualTo(3) + + interactor.level.value = 1 + assertThat(latest!!.level).isEqualTo(1) + + job.cancel() + } + + @Test + fun icon_usesNumberOfLevelsFromInteractor() = + testScope.runTest { + var latest: SignalIconModel? = null + val job = underTest.icon.onEach { latest = it }.launchIn(this) + + interactor.numberOfLevels.value = 5 + assertThat(latest!!.numberOfLevels).isEqualTo(5) + + interactor.numberOfLevels.value = 2 + assertThat(latest!!.numberOfLevels).isEqualTo(2) + + job.cancel() + } + + @Test + fun icon_defaultDataDisabled_showExclamationTrue() = testScope.runTest { interactor.setIsDefaultDataEnabled(false) var latest: SignalIconModel? = null val job = underTest.icon.onEach { latest = it }.launchIn(this) - val expected = defaultSignal(level = 1, connected = false) - assertThat(latest).isEqualTo(expected) + assertThat(latest!!.showExclamationMark).isTrue() + + job.cancel() + } + + @Test + fun icon_defaultConnectionFailed_showExclamationTrue() = + testScope.runTest { + interactor.isDefaultConnectionFailed.value = true + + var latest: SignalIconModel? = null + val job = underTest.icon.onEach { latest = it }.launchIn(this) + + assertThat(latest!!.showExclamationMark).isTrue() + + job.cancel() + } + + @Test + fun icon_enabledAndNotFailed_showExclamationFalse() = + testScope.runTest { + interactor.setIsDefaultDataEnabled(true) + interactor.isDefaultConnectionFailed.value = false + + var latest: SignalIconModel? = null + val job = underTest.icon.onEach { latest = it }.launchIn(this) + + assertThat(latest!!.showExclamationMark).isFalse() job.cancel() } @@ -572,16 +628,14 @@ class MobileIconViewModelTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 + private const val NUM_LEVELS = 4 /** Convenience constructor for these tests */ - fun defaultSignal( - level: Int = 1, - connected: Boolean = true, - ): SignalIconModel { - return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected) + fun defaultSignal(level: Int = 1): SignalIconModel { + return SignalIconModel(level, NUM_LEVELS, showExclamationMark = false) } fun emptySignal(): SignalIconModel = - SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true) + SignalIconModel(level = 0, numberOfLevels = NUM_LEVELS, showExclamationMark = true) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index ddb7f4d88d30..01bec879102d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -32,9 +32,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -69,14 +68,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), ) - val subscriptionIdsFlow = - interactor.filteredSubscriptions - .map { subs -> subs.map { it.subscriptionId } } - .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf()) - underTest = MobileIconsViewModel( - subscriptionIdsFlow, logger, verboseLogger, interactor, @@ -90,6 +83,32 @@ class MobileIconsViewModelTest : SysuiTestCase() { } @Test + fun subscriptionIdsFlow_matchesInteractor() = + testScope.runTest { + var latest: List<Int>? = null + val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = + listOf( + SubscriptionModel(subscriptionId = 1, isOpportunistic = false), + ) + assertThat(latest).isEqualTo(listOf(1)) + + interactor.filteredSubscriptions.value = + listOf( + SubscriptionModel(subscriptionId = 2, isOpportunistic = false), + SubscriptionModel(subscriptionId = 5, isOpportunistic = true), + SubscriptionModel(subscriptionId = 7, isOpportunistic = true), + ) + assertThat(latest).isEqualTo(listOf(2, 5, 7)) + + interactor.filteredSubscriptions.value = emptyList() + assertThat(latest).isEmpty() + + job.cancel() + } + + @Test fun `caching - mobile icon view model is reused for same sub id`() = testScope.runTest { val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index ddc6d484d93f..d30e0246c2dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -29,6 +29,7 @@ import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback +import android.net.wifi.WifiManager.UNKNOWN_SSID import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -49,16 +50,14 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import org.junit.After +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.anyInt @@ -80,9 +79,10 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var wifiManager: WifiManager private lateinit var executor: Executor - private lateinit var scope: CoroutineScope private lateinit var connectivityRepository: ConnectivityRepository + private val testScope = TestScope(UnconfinedTestDispatcher()) + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -96,7 +96,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { ) .thenReturn(flowOf(Unit)) executor = FakeExecutor(FakeSystemClock()) - scope = CoroutineScope(IMMEDIATE) connectivityRepository = ConnectivityRepositoryImpl( @@ -105,21 +104,16 @@ class WifiRepositoryImplTest : SysuiTestCase() { context, mock(), mock(), - scope, + testScope.backgroundScope, mock(), ) underTest = createRepo() } - @After - fun tearDown() { - scope.cancel() - } - @Test fun isWifiEnabled_initiallyGetsWifiManagerValue() = - runBlocking(IMMEDIATE) { + testScope.runTest { whenever(wifiManager.isWifiEnabled).thenReturn(true) underTest = createRepo() @@ -129,7 +123,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = - runBlocking(IMMEDIATE) { + testScope.runTest { // We need to call launch on the flows so that they start updating val networkJob = underTest.wifiNetwork.launchIn(this) val enabledJob = underTest.isWifiEnabled.launchIn(this) @@ -152,7 +146,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiEnabled_networkLost_valueUpdated() = - runBlocking(IMMEDIATE) { + testScope.runTest { // We need to call launch on the flows so that they start updating val networkJob = underTest.wifiNetwork.launchIn(this) val enabledJob = underTest.isWifiEnabled.launchIn(this) @@ -173,7 +167,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiEnabled_intentsReceived_valueUpdated() = - runBlocking(IMMEDIATE) { + testScope.runTest { val intentFlow = MutableSharedFlow<Unit>() whenever( broadcastDispatcher.broadcastFlow( @@ -203,7 +197,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = - runBlocking(IMMEDIATE) { + testScope.runTest { val intentFlow = MutableSharedFlow<Unit>() whenever( broadcastDispatcher.broadcastFlow( @@ -242,7 +236,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_initiallyGetsDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) assertThat(underTest.isWifiDefault.value).isFalse() @@ -252,7 +246,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_wifiNetwork_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) } @@ -268,7 +262,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { /** Regression test for b/266628069. */ @Test fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val transportInfo = @@ -294,7 +288,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { /** Regression test for b/266628069. */ @Test fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val transportInfo = @@ -319,7 +313,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_carrierMergedViaCellular_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val carrierMergedInfo = @@ -341,7 +335,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val capabilities = @@ -360,7 +354,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_carrierMergedViaWifi_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val carrierMergedInfo = @@ -382,7 +376,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val capabilities = @@ -401,7 +395,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val capabilities = @@ -420,7 +414,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_cellularNotVcnNetwork_isFalse() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val capabilities = @@ -438,7 +432,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val underlyingNetwork = mock<Network>() @@ -473,7 +467,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val underlyingCarrierMergedNetwork = mock<Network>() @@ -507,7 +501,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_wifiNetworkLost_isFalse() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) // First, add a network @@ -526,7 +520,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_initiallyGetsDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -537,7 +531,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -561,7 +555,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -581,7 +575,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -618,7 +612,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -656,7 +650,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -680,7 +674,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_isCarrierMerged_getsCorrectValues() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -715,7 +709,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_notValidated_networkNotValidated() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -732,7 +726,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_validated_networkValidated() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -749,7 +743,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -770,7 +764,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { /** Regression test for b/266628069. */ @Test fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -789,7 +783,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -811,7 +805,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -835,7 +829,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -854,7 +848,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_cellularAndWifiTransports_usesCellular() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -877,7 +871,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -910,7 +904,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -943,7 +937,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -986,7 +980,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -1001,7 +995,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -1020,7 +1014,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -1043,7 +1037,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -1069,7 +1063,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { /** Regression test for b/244173280. */ @Test fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest1: WifiNetworkModel? = null val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this) @@ -1096,8 +1090,151 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun isWifiConnectedWithValidSsid_inactiveNetwork_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + // A non-primary network is inactive + whenever(this.isPrimary).thenReturn(false) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_invalidNetwork_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo), + ) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.ssid).thenReturn(null) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.ssid).thenReturn(UNKNOWN_SSID) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.ssid).thenReturn("FakeSsid") + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + // Start with active + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.ssid).thenReturn("FakeSsid") + } + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue() + + // WHEN the network is lost + getNetworkCallback().onLost(NETWORK) + + // THEN the isWifiConnected updates + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test fun wifiActivity_callbackGivesNone_activityFlowHasNone() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: DataActivityModel? = null val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) @@ -1111,7 +1248,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesIn_activityFlowHasIn() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: DataActivityModel? = null val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) @@ -1125,7 +1262,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesOut_activityFlowHasOut() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: DataActivityModel? = null val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) @@ -1139,7 +1276,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: DataActivityModel? = null val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) @@ -1159,7 +1296,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { logger, tableLogger, executor, - scope, + testScope.backgroundScope, wifiManager, ) } @@ -1204,5 +1341,3 @@ class WifiRepositoryImplTest : SysuiTestCase() { } } } - -private val IMMEDIATE = Dispatchers.Main.immediate 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 ab4e93ceee84..4e0c309512e8 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 @@ -16,6 +16,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.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -50,6 +51,42 @@ class WifiNetworkModelTest : SysuiTestCase() { WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1) } + @Test + fun active_hasValidSsid_nullSsid_false() { + val network = + WifiNetworkModel.Active( + NETWORK_ID, + level = MAX_VALID_LEVEL, + ssid = null, + ) + + assertThat(network.hasValidSsid()).isFalse() + } + + @Test + fun active_hasValidSsid_unknownSsid_false() { + val network = + WifiNetworkModel.Active( + NETWORK_ID, + level = MAX_VALID_LEVEL, + ssid = UNKNOWN_SSID, + ) + + assertThat(network.hasValidSsid()).isFalse() + } + + @Test + fun active_hasValidSsid_validSsid_true() { + val network = + WifiNetworkModel.Active( + NETWORK_ID, + level = MAX_VALID_LEVEL, + ssid = "FakeSsid", + ) + + assertThat(network.hasValidSsid()).isTrue() + } + // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java index 1eee08c22187..91c88cebff79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java @@ -21,6 +21,7 @@ import static android.os.BatteryManager.EXTRA_PRESENT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -167,8 +168,10 @@ public class BatteryControllerTest extends SysuiTestCase { mBatteryController.setPowerSaveMode(false, mView); StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class)); - inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true)); - inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true)); + inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true, + SAVER_ENABLED_QS)); + inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true, + SAVER_ENABLED_QS)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 01e94baab7c3..391c8ca4d286 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -62,7 +62,7 @@ import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; import androidx.annotation.NonNull; -import androidx.core.animation.AnimatorTestRule2; +import androidx.core.animation.AnimatorTestRule; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; @@ -110,7 +110,7 @@ public class RemoteInputViewTest extends SysuiTestCase { private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); @ClassRule - public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2(); + public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); @Before public void setUp() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java new file mode 100644 index 000000000000..9cf3e443320d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java @@ -0,0 +1,91 @@ +/* + * 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.volume; + +import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X; +import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.Notification; +import android.app.NotificationManager; +import android.media.AudioManager; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.messages.nano.SystemMessageProto; +import com.android.systemui.SysuiTestCase; +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; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class CsdWarningDialogTest extends SysuiTestCase { + + private NotificationManager mNotificationManager; + private AudioManager mAudioManager; + + @Before + public void setup() { + mNotificationManager = mock(NotificationManager.class); + getContext().addMockSystemService(NotificationManager.class, mNotificationManager); + + mAudioManager = mock(AudioManager.class); + getContext().addMockSystemService(AudioManager.class, mAudioManager); + } + + @Test + public void create1XCsdDialogAndWait_sendsNotification() { + FakeExecutor executor = new FakeExecutor(new FakeSystemClock()); + // instantiate directly instead of via factory; we don't want executor to be @Background + CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext, + mAudioManager, mNotificationManager, executor, null); + + dialog.show(); + executor.advanceClockToLast(); + executor.runAllReady(); + dialog.dismiss(); + + verify(mNotificationManager).notify( + eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class)); + } + + @Test + public void create5XCsdDiSalogAndWait_willNotSendNotification() { + FakeExecutor executor = new FakeExecutor(new FakeSystemClock()); + CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext, + mAudioManager, mNotificationManager, executor, null); + + dialog.show(); + executor.advanceClockToLast(); + executor.runAllReady(); + dialog.dismiss(); + + verify(mNotificationManager, never()).notify( + eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class)); + } +} 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 d419095921b8..eb2688894cb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -102,6 +102,15 @@ public class VolumeDialogImplTest extends SysuiTestCase { InteractionJankMonitor mInteractionJankMonitor; @Mock private DumpManager mDumpManager; + @Mock CsdWarningDialog mCsdWarningDialog; + + private final CsdWarningDialog.Factory mCsdWarningDialogFactory = + new CsdWarningDialog.Factory() { + @Override + public CsdWarningDialog create(int warningType, Runnable onCleanup) { + return mCsdWarningDialog; + } + }; @Before public void setup() throws Exception { @@ -124,6 +133,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { mInteractionJankMonitor, mDeviceConfigProxy, mExecutor, + mCsdWarningDialogFactory, mDumpManager ); mDialog.init(0, null); @@ -352,6 +362,14 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.getDialogView().animate().cancel(); } + @Test + public void showCsdWarning_dialogShown() { + mDialog.showCsdWarningH(AudioManager.CSD_WARNING_DOSE_REACHED_1X, + CsdWarningDialog.NO_ACTION_TIMEOUT_MS); + + verify(mCsdWarningDialog).show(); + } + /* @Test public void testContentDescriptions() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt new file mode 100644 index 000000000000..c08ecd0e3b0c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -0,0 +1,59 @@ +/* + * 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.keyguard.data.repository + +import com.android.keyguard.FaceAuthUiEvent +import com.android.systemui.keyguard.shared.model.AuthenticationStatus +import com.android.systemui.keyguard.shared.model.DetectionStatus +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { + + override val isAuthenticated = MutableStateFlow(false) + override val canRunFaceAuth = MutableStateFlow(false) + private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null) + override val authenticationStatus: Flow<AuthenticationStatus> = + _authenticationStatus.filterNotNull() + fun setAuthenticationStatus(status: AuthenticationStatus) { + _authenticationStatus.value = status + } + private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null) + override val detectionStatus: Flow<DetectionStatus> + get() = _detectionStatus.filterNotNull() + fun setDetectionStatus(status: DetectionStatus) { + _detectionStatus.value = status + } + override val isLockedOut = MutableStateFlow(false) + private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null) + val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> = + _runningAuthRequest.asStateFlow() + override val isAuthRunning = _runningAuthRequest.map { it != null } + override val isBypassEnabled = MutableStateFlow(false) + + override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + _runningAuthRequest.value = uiEvent to fallbackToDetection + } + + override fun cancel() { + _runningAuthRequest.value = null + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt new file mode 100644 index 000000000000..9383a0a68844 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt @@ -0,0 +1,36 @@ +/* + * 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.qs + +import android.content.Context +import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.QSTileView + +class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory { + override fun createTile(tileSpec: String): QSTile? { + return tileCreator(tileSpec) + } + + override fun createTileView( + context: Context?, + tile: QSTile?, + collapsedView: Boolean + ): QSTileView { + throw NotImplementedError("Not implemented") + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt new file mode 100644 index 000000000000..777130409aad --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt @@ -0,0 +1,36 @@ +/* + * 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.qs.pipeline.data.repository + +import android.content.ComponentName + +class FakeCustomTileAddedRepository : CustomTileAddedRepository { + + private val tileAddedRegistry = mutableSetOf<Pair<Int, ComponentName>>() + + override fun isTileAdded(componentName: ComponentName, userId: Int): Boolean { + return (userId to componentName) in tileAddedRegistry + } + + override fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean) { + if (added) { + tileAddedRegistry.add(userId to componentName) + } else { + tileAddedRegistry.remove(userId to componentName) + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt new file mode 100644 index 000000000000..2865710c2eae --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt @@ -0,0 +1,61 @@ +/* + * 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.qs.pipeline.data.repository + +import android.util.Log +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END +import com.android.systemui.qs.pipeline.shared.TileSpec +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeTileSpecRepository : TileSpecRepository { + + private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>() + + override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> { + return getFlow(userId).asStateFlow().also { Log.d("Fabian", "Retrieving flow for $userId") } + } + + override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) { + if (tile == TileSpec.Invalid) return + with(getFlow(userId)) { + value = + value.toMutableList().apply { + if (position == POSITION_AT_END) { + add(tile) + } else { + add(position, tile) + } + } + } + } + + override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) { + with(getFlow(userId)) { + value = + value.toMutableList().apply { removeAll(tiles.filter { it != TileSpec.Invalid }) } + } + } + + override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) { + getFlow(userId).value = tiles.filter { it != TileSpec.Invalid } + } + + private fun getFlow(userId: Int): MutableStateFlow<List<TileSpec>> = + tilesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) } +} diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk index cbca3f03fb0f..a41d0e57cd21 100644 --- a/packages/overlays/Android.mk +++ b/packages/overlays/Android.mk @@ -32,6 +32,7 @@ LOCAL_REQUIRED_MODULES := \ NavigationBarModeGesturalOverlayWideBack \ NavigationBarModeGesturalOverlayExtraWideBack \ TransparentNavigationBarOverlay \ + NotesRoleEnabledOverlay \ preinstalled-packages-platform-overlays.xml include $(BUILD_PHONY_PACKAGE) diff --git a/packages/overlays/NotesRoleEnabledOverlay/Android.bp b/packages/overlays/NotesRoleEnabledOverlay/Android.bp new file mode 100644 index 000000000000..68ebd9652399 --- /dev/null +++ b/packages/overlays/NotesRoleEnabledOverlay/Android.bp @@ -0,0 +1,30 @@ +// +// Copyright 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +runtime_resource_overlay { + name: "NotesRoleEnabledOverlay", + theme: "NotesRoleEnabled", + product_specific: true, +} diff --git a/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..c01178d1727d --- /dev/null +++ b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml @@ -0,0 +1,26 @@ +<!-- +/** + * 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. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.role.notes.enabled" + android:versionCode="1" + android:versionName="1.0"> + <overlay android:targetPackage="android" + android:priority="1"/> + + <application android:label="@string/notes_role_enabled_overlay_title" android:hasCode="false"/> +</manifest>
\ No newline at end of file diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml new file mode 100644 index 000000000000..f27f5f42ae6b --- /dev/null +++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * 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. + */ +--> +<resources> + <!-- Whether the default notes role should be enabled. In builds without + device-specific overlays, this can be controlled in developer options. --> + <bool name="config_enableDefaultNotes">true</bool> + + <!-- Whether the default notes role for work profile should be enabled. + In builds without device-specific overlays, this can be controlled in + developer options. --> + <bool name="config_enableDefaultNotesForWorkProfile">true</bool> +</resources> diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml new file mode 100644 index 000000000000..3edbb571c4d1 --- /dev/null +++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Name of overlay [CHAR LIMIT=64] --> + <string name="notes_role_enabled_overlay_title" translatable="false">Notes Role enabled</string> +</resources>
\ No newline at end of file diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 47027342974d..21d09792f1c7 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -402,5 +402,7 @@ message SystemMessage { // Package: android NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF = 1006; + // Notify the user that audio was lowered based on Calculated Sound Dose (CSD) + NOTE_CSD_LOWER_AUDIO = 1007; } } diff --git a/services/Android.bp b/services/Android.bp index 6e6c55325e3d..b0a0e5e44a8c 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -112,6 +112,7 @@ filegroup { ":services.searchui-sources", ":services.selectiontoolbar-sources", ":services.smartspace-sources", + ":services.soundtrigger-sources", ":services.systemcaptions-sources", ":services.translation-sources", ":services.texttospeech-sources", @@ -169,6 +170,7 @@ java_library { "services.searchui", "services.selectiontoolbar", "services.smartspace", + "services.soundtrigger", "services.systemcaptions", "services.translation", "services.texttospeech", diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 8d039fc02026..2a964b8b701f 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2073,15 +2073,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onSaveRequestSuccess(@NonNull String servicePackageName, @Nullable IntentSender intentSender) { - // Log onSaveRequest result. - mSaveEventLogger.maybeSetIsSaved(true); - final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime; - mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); - mSaveEventLogger.logAndEndEvent(); - synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; - + // Log onSaveRequest result. + mSaveEventLogger.maybeSetIsSaved(true); + final long saveRequestFinishTimestamp = + SystemClock.elapsedRealtime() - mLatencyBaseTime; + mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); + mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " + id + " destroyed"); @@ -2108,14 +2107,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull String servicePackageName) { boolean showMessage = !TextUtils.isEmpty(message); - // Log onSaveRequest result. - final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime; - mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); - mSaveEventLogger.logAndEndEvent(); - synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; - + // Log onSaveRequest result. + final long saveRequestFinishTimestamp = + SystemClock.elapsedRealtime() - mLatencyBaseTime; + mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); + mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " + id + " destroyed"); @@ -2228,8 +2226,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override public void save() { - mSaveEventLogger.maybeSetSaveButtonClicked(true); synchronized (mLock) { + mSaveEventLogger.maybeSetSaveButtonClicked(true); if (mDestroyed) { Slog.w(TAG, "Call to Session#save() rejected - session: " + id + " destroyed"); @@ -2247,10 +2245,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override public void cancelSave() { - mSaveEventLogger.maybeSetDialogDismissed(true); synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; - + mSaveEventLogger.maybeSetDialogDismissed(true); if (mDestroyed) { Slog.w(TAG, "Call to Session#cancelSave() rejected - session: " + id + " destroyed"); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 542cc2f0a0a6..5b320a87d113 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -19,6 +19,7 @@ package com.android.server.companion; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; +import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; @@ -1066,6 +1067,10 @@ public class CompanionDeviceManagerService extends SystemService { // No role was granted to for this association, there is nothing else we need to here. return true; } + // Do not need to remove the system role since it was pre-granted by the system. + if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) { + return true; + } // Check if the applications is associated with another devices with the profile. If so, // it should remain the role holder. diff --git a/services/core/Android.bp b/services/core/Android.bp index c8caab93d76c..cfdf3ac5915b 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -174,7 +174,6 @@ java_library_static { "android.hardware.configstore-V1.1-java", "android.hardware.ir-V1-java", "android.hardware.rebootescrow-V1-java", - "android.hardware.soundtrigger-V2.3-java", "android.hardware.power.stats-V2-java", "android.hardware.power-V4-java", "android.hidl.manager-V1.2-java", diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 3ecf93328219..8fc30e43d1a0 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -80,7 +80,7 @@ import android.util.apk.ApkSignatureVerifier; import android.util.apk.ApkSigningBlockUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.expresslog.Histogram; +import com.android.modules.expresslog.Histogram; import com.android.internal.os.IBinaryTransparencyService; import com.android.internal.util.FrameworkStatsLog; import com.android.server.pm.ApexManager; diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 92889c05d9f4..d256aead97e8 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -426,7 +426,7 @@ public class PackageWatchdog { } int impact = registeredObserver.onHealthCheckFailed( versionedPackage, failureReason, mitigationCount); - if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE + if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 && impact < currentObserverImpact) { currentObserverToNotify = registeredObserver; currentObserverImpact = impact; @@ -466,7 +466,7 @@ public class PackageWatchdog { if (registeredObserver != null) { int impact = registeredObserver.onHealthCheckFailed( failingPackage, failureReason, 1); - if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE + if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 && impact < currentObserverImpact) { currentObserverToNotify = registeredObserver; currentObserverImpact = impact; @@ -494,7 +494,7 @@ public class PackageWatchdog { PackageHealthObserver registeredObserver = observer.registeredObserver; if (registeredObserver != null) { int impact = registeredObserver.onBootLoop(mitigationCount); - if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE + if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 && impact < currentObserverImpact) { currentObserverToNotify = registeredObserver; currentObserverImpact = impact; @@ -576,19 +576,23 @@ public class PackageWatchdog { /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */ @Retention(SOURCE) - @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE, - PackageHealthObserverImpact.USER_IMPACT_LOW, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM, - PackageHealthObserverImpact.USER_IMPACT_HIGH}) + @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100}) public @interface PackageHealthObserverImpact { /** No action to take. */ - int USER_IMPACT_NONE = 0; + int USER_IMPACT_LEVEL_0 = 0; /* Action has low user impact, user of a device will barely notice. */ - int USER_IMPACT_LOW = 1; - /* Action has medium user impact, user of a device will likely notice. */ - int USER_IMPACT_MEDIUM = 3; + int USER_IMPACT_LEVEL_10 = 10; + /* Actions having medium user impact, user of a device will likely notice. */ + int USER_IMPACT_LEVEL_30 = 30; + int USER_IMPACT_LEVEL_50 = 50; + int USER_IMPACT_LEVEL_70 = 70; /* Action has high user impact, a last resort, user of a device will be very frustrated. */ - int USER_IMPACT_HIGH = 5; + int USER_IMPACT_LEVEL_100 = 100; } /** Register instances of this interface to receive notifications on package failure. */ @@ -633,7 +637,7 @@ public class PackageWatchdog { * boot loop (including this time). */ default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) { - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } /** diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index 3de65f94decf..6e2e5f75b8f3 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -107,7 +107,7 @@ public class RescueParty { static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = "namespace_to_package_mapping"; @VisibleForTesting - static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10); + static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10; private static final String NAME = "rescue-party-observer"; @@ -117,6 +117,8 @@ public class RescueParty { "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = "persist.device_config.configuration.disable_rescue_party_factory_reset"; + private static final String PROP_THROTTLE_DURATION_MIN_FLAG = + "persist.device_config.configuration.rescue_party_throttle_duration_min"; private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -489,13 +491,14 @@ public class RescueParty { switch(rescueLevel) { case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - return PackageHealthObserverImpact.USER_IMPACT_LOW; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: case LEVEL_WARM_REBOOT: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; case LEVEL_FACTORY_RESET: - return PackageHealthObserverImpact.USER_IMPACT_HIGH; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; default: - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } } @@ -633,7 +636,7 @@ public class RescueParty { return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, mayPerformReboot(failedPackage))); } else { - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } } @@ -677,7 +680,7 @@ public class RescueParty { @Override public int onBootLoop(int mitigationCount) { if (isDisabled()) { - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true)); } @@ -721,7 +724,9 @@ public class RescueParty { private boolean shouldThrottleReboot() { Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0); long now = System.currentTimeMillis(); - return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS; + long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG, + DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN); + return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin); } private boolean isPersistentSystemApp(@NonNull String packageName) { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java index cc398d930c7e..e6c1750c4a1d 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java +++ b/services/core/java/com/android/server/SoundTriggerInternal.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.soundtrigger; +package com.android.server; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,15 +29,13 @@ import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.media.permission.Identity; import android.os.IBinder; -import com.android.server.voiceinteraction.VoiceInteractionManagerService; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; /** * Provides a local service for managing voice-related recoginition models. This is primarily used - * by the {@link VoiceInteractionManagerService}. + * by the {@code VoiceInteractionManagerService}. */ public interface SoundTriggerInternal { /** diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 7b618b11bd45..e248007eca19 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -26,6 +26,17 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE; import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED; import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED; import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK; @@ -117,6 +128,7 @@ import android.annotation.UptimeMillisLong; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityManagerInternal.ServiceNotificationPolicy; import android.app.ActivityThread; import android.app.AppGlobals; @@ -1146,7 +1158,7 @@ public final class ActiveServices { } finally { /* Will be a no-op if nothing pending */ mAm.updateOomAdjPendingTargetsLocked( - OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + OOM_ADJ_REASON_START_SERVICE); } } else { unbindServiceLocked(connection); @@ -1236,8 +1248,7 @@ public final class ActiveServices { /* ignore - local call */ } finally { /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked( - OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); } } else { // Starting a service try { @@ -1311,7 +1322,7 @@ public final class ActiveServices { false /* packageFrozen */, true /* enqueueOomAdj */); /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); if (error != null) { return new ComponentName("!!", error); } @@ -1496,7 +1507,7 @@ public final class ActiveServices { stopServiceLocked(service, true); } if (size > 0) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE); } } } @@ -3296,7 +3307,7 @@ public final class ActiveServices { Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr); - mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT); + mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT); } } @@ -3630,7 +3641,7 @@ public final class ActiveServices { needOomAdj = true; if (bringUpServiceLocked(s, service.getFlags(), callerFg, false, permissionsReviewRequired, packageFrozen, true) != null) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE); return 0; } } @@ -3655,7 +3666,7 @@ public final class ActiveServices { mAm.enqueueOomAdjTargetLocked(s.app); } if (needOomAdj) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE); } final int packageState = wasStopped @@ -3787,7 +3798,8 @@ public final class ActiveServices { } } - serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false); + serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false, + OOM_ADJ_REASON_EXECUTING_SERVICE); } } finally { Binder.restoreCallingIdentity(origId); @@ -3878,7 +3890,7 @@ public final class ActiveServices { } } - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE); } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); @@ -3925,7 +3937,8 @@ public final class ActiveServices { } } - serviceDoneExecutingLocked(r, inDestroying, false, false); + serviceDoneExecutingLocked(r, inDestroying, false, false, + OOM_ADJ_REASON_UNBIND_SERVICE); } } finally { Binder.restoreCallingIdentity(origId); @@ -4360,11 +4373,11 @@ public final class ActiveServices { /** * Bump the given service record into executing state. * @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link - * OomAdjuster#OOM_ADJ_REASON_NONE}. + * ActivityManagerInternal#OOM_ADJ_REASON_NONE}. * @return {@code true} if it performed oomAdjUpdate. */ private boolean bumpServiceExecutingLocked( - ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) { + ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING " + why + " of " + r + " in app " + r.app); else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING " @@ -4416,7 +4429,7 @@ public final class ActiveServices { } } boolean oomAdjusted = false; - if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null + if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null && r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) { // Force an immediate oomAdjUpdate, so the client app could be in the correct process // state before doing any service related transactions @@ -4440,8 +4453,7 @@ public final class ActiveServices { + " rebind=" + rebind); if ((!i.requested || rebind) && i.apps.size() > 0) { try { - bumpServiceExecutingLocked(r, execInFg, "bind", - OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE); + bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE); if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding=" + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter); @@ -4457,13 +4469,15 @@ public final class ActiveServices { // Keep the executeNesting count accurate. if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e); final boolean inDestroying = mDestroyingServices.contains(r); - serviceDoneExecutingLocked(r, inDestroying, inDestroying, false); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, + OOM_ADJ_REASON_UNBIND_SERVICE); throw e; } catch (RemoteException e) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r); // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); - serviceDoneExecutingLocked(r, inDestroying, inDestroying, false); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, + OOM_ADJ_REASON_UNBIND_SERVICE); return false; } } @@ -4841,7 +4855,7 @@ public final class ActiveServices { // Ignore, it's been logged and nothing upstack cares. } finally { /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); } } @@ -5193,13 +5207,14 @@ public final class ActiveServices { final ProcessServiceRecord psr = app.mServices; final boolean newService = psr.startService(r); - bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE); + bumpServiceExecutingLocked(r, execInFg, "create", + OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */); mAm.updateLruProcessLocked(app, false, null); updateServiceForegroundLocked(psr, /* oomAdj= */ false); // Force an immediate oomAdjUpdate, so the client app could be in the correct process state // before doing any service related transactions mAm.enqueueOomAdjTargetLocked(app); - mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE); boolean created = false; try { @@ -5233,7 +5248,8 @@ public final class ActiveServices { if (!created) { // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); - serviceDoneExecutingLocked(r, inDestroying, inDestroying, false); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, + OOM_ADJ_REASON_STOP_SERVICE); // Cleanup. if (newService) { @@ -5319,7 +5335,8 @@ public final class ActiveServices { mAm.grantImplicitAccess(r.userId, si.intent, si.callingId, UserHandle.getAppId(r.appInfo.uid) ); - bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE); + bumpServiceExecutingLocked(r, execInFg, "start", + OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */); if (r.fgRequired && !r.fgWaiting) { if (!r.isForeground) { if (DEBUG_BACKGROUND_CHECK) { @@ -5345,7 +5362,7 @@ public final class ActiveServices { if (!oomAdjusted) { mAm.enqueueOomAdjTargetLocked(r.app); - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); } ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args); slice.setInlineCountLimit(4); @@ -5371,10 +5388,11 @@ public final class ActiveServices { // Keep nesting count correct final boolean inDestroying = mDestroyingServices.contains(r); for (int i = 0, size = args.size(); i < size; i++) { - serviceDoneExecutingLocked(r, inDestroying, inDestroying, true); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, true, + OOM_ADJ_REASON_STOP_SERVICE); } /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE); if (caughtException instanceof TransactionTooLargeException) { throw (TransactionTooLargeException)caughtException; } @@ -5461,7 +5479,7 @@ public final class ActiveServices { if (ibr.hasBound) { try { oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind", - OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + OOM_ADJ_REASON_UNBIND_SERVICE); ibr.hasBound = false; ibr.requested = false; r.app.getThread().scheduleUnbindService(r, @@ -5615,7 +5633,7 @@ public final class ActiveServices { } else { try { oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy", - oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE); mDestroyingServices.add(r); r.destroying = true; r.app.getThread().scheduleStopService(r); @@ -5637,7 +5655,7 @@ public final class ActiveServices { if (!oomAdjusted) { mAm.enqueueOomAdjTargetLocked(r.app); if (!enqueueOomAdj) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE); } } if (r.bindings.size() > 0) { @@ -5762,8 +5780,7 @@ public final class ActiveServices { if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0 && b.intent.hasBound) { try { - bumpServiceExecutingLocked(s, false, "unbind", - OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE); if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY) && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) { // If this service's process is not already in the cached list, @@ -5886,7 +5903,8 @@ public final class ActiveServices { } } final long origId = Binder.clearCallingIdentity(); - serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj, + OOM_ADJ_REASON_EXECUTING_SERVICE); Binder.restoreCallingIdentity(origId); } else { Slog.w(TAG, "Done executing unknown service from pid " @@ -5905,11 +5923,11 @@ public final class ActiveServices { r.tracker.setStarted(false, memFactor, now); } } - serviceDoneExecutingLocked(r, true, true, enqueueOomAdj); + serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END); } private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, - boolean finishing, boolean enqueueOomAdj) { + boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r + ": nesting=" + r.executeNesting + ", inDestroying=" + inDestroying + ", app=" + r.app); @@ -5945,7 +5963,7 @@ public final class ActiveServices { if (enqueueOomAdj) { mAm.enqueueOomAdjTargetLocked(r.app); } else { - mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjLocked(r.app, oomAdjReason); } } r.executeFg = false; @@ -6015,7 +6033,7 @@ public final class ActiveServices { bringDownServiceLocked(sr, true); } /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); } } catch (RemoteException e) { Slog.w(TAG, "Exception in new application when starting service " @@ -6075,7 +6093,7 @@ public final class ActiveServices { } } if (needOomAdj) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END); } } @@ -6146,7 +6164,7 @@ public final class ActiveServices { bringDownServiceLocked(mTmpCollectionResults.get(i), true); } if (size > 0) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED); } if (fullStop && !mTmpCollectionResults.isEmpty()) { // if we're tearing down the app's entire service state, account for possible @@ -6273,7 +6291,7 @@ public final class ActiveServices { } } if (needOomAdj) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK); } } @@ -6444,7 +6462,7 @@ public final class ActiveServices { } } - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE); if (!allowRestart) { psr.stopAllServices(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ef7d5ae43396..b32f8c973e6a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -44,6 +44,14 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_NONE; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; @@ -199,6 +207,7 @@ import android.app.ActivityManagerInternal.BindServiceEventListener; import android.app.ActivityManagerInternal.BroadcastEventListener; import android.app.ActivityManagerInternal.ForegroundServiceStateListener; import android.app.ActivityManagerInternal.MediaProjectionTokenEvent; +import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.ActivityThread; import android.app.AnrController; @@ -368,7 +377,6 @@ import android.util.FeatureFlagUtils; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.Log; -import android.util.LogWriter; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -1968,7 +1976,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM); addPidLocked(app); updateLruProcessLocked(app, false, null); - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT); } } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException( @@ -2502,7 +2510,7 @@ public class ActivityManagerService extends IActivityManager.Stub // bind background threads to little cores // this is expected to fail inside of framework tests because apps can't touch cpusets directly // make sure we've already adjusted system_server's internal view of itself first - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT); try { Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(), Process.THREAD_GROUP_SYSTEM); @@ -3387,7 +3395,7 @@ public class ActivityManagerService extends IActivityManager.Stub handleAppDiedLocked(app, pid, false, true, fromBinderDied); if (doOomAdj) { - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END); + updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END); } if (doLowMem) { mAppProfiler.doLowMemReportIfNeededLocked(app); @@ -4843,7 +4851,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (!didSomething) { - updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); + updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked"); } @@ -5485,7 +5493,7 @@ public class ActivityManagerService extends IActivityManager.Stub "setProcessLimit()"); synchronized (this) { mConstants.setOverrideMaxCachedProcesses(max); - trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END); + trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END); } } @@ -5513,7 +5521,7 @@ public class ActivityManagerService extends IActivityManager.Stub pr.mState.setForcingToImportant(null); clearProcessForegroundLocked(pr); } - updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY); } } @@ -5560,7 +5568,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (changed) { - updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY); } } } @@ -6869,7 +6877,7 @@ public class ActivityManagerService extends IActivityManager.Stub new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION, customProcess != null ? customProcess : info.processName)); updateLruProcessLocked(app, false, null); - updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); + updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); } // Report usage as process is persistent and being started. @@ -6986,7 +6994,7 @@ public class ActivityManagerService extends IActivityManager.Stub mOomAdjProfiler.onWakefulnessChanged(wakefulness); mOomAdjuster.onWakefulnessChanged(wakefulness); - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY); } } } @@ -7748,7 +7756,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } if (changed) { - updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY); } } } finally { @@ -8728,7 +8736,9 @@ public class ActivityManagerService extends IActivityManager.Stub // 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes, // debuggerd will terminate the process, but there's a backup where ActivityManager will // also kill it. Avoid that. - if (!recoverable) { + if (recoverable) { + mAppErrors.sendRecoverableCrashToAppExitInfo(r, crashInfo); + } else { mAppErrors.crashApplication(r, crashInfo); } } @@ -9508,7 +9518,7 @@ public class ActivityManagerService extends IActivityManager.Stub mAppProfiler.setMemFactorOverrideLocked(level); // Kick off an oom adj update since we forced a mem factor update. - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(OOM_ADJ_REASON_SHELL); } } @@ -13408,7 +13418,7 @@ public class ActivityManagerService extends IActivityManager.Stub proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP); // Try not to kill the process during backup - updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP); // If the process is already attached, schedule the creation of the backup agent now. // If it is not yet live, this will be done when it attaches to the framework. @@ -13532,7 +13542,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Not backing this app up any more; reset its OOM adjustment final ProcessRecord proc = backupTarget.app; - updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP); proc.setInFullBackup(false); proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP); @@ -13713,7 +13723,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver( /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) { throw new SecurityException("SDK sandbox not allowed to register receiver" - + " with the given IntentFilter: " + filter.toString()); + + " with the given IntentFilter: " + filter.toLongString()); } } @@ -13923,7 +13933,7 @@ public class ActivityManagerService extends IActivityManager.Stub // If we actually concluded any broadcasts, we might now be able // to trim the recipients' apps from our working set if (doTrim) { - trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER); + trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER); return; } } @@ -15185,7 +15195,7 @@ public class ActivityManagerService extends IActivityManager.Stub queue.finishReceiverLocked(callerApp, resultCode, resultData, resultExtras, resultAbort, true); // updateOomAdjLocked() will be done here - trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER); + trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER); } } finally { @@ -16130,7 +16140,7 @@ public class ActivityManagerService extends IActivityManager.Stub item.foregroundServiceTypes = fgServiceTypes; } if (oomAdj) { - updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY); } } @@ -16196,7 +16206,7 @@ public class ActivityManagerService extends IActivityManager.Stub * {@link #enqueueOomAdjTargetLocked}. */ @GuardedBy("this") - void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) { + void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) { mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason); } @@ -16215,7 +16225,7 @@ public class ActivityManagerService extends IActivityManager.Stub } @GuardedBy("this") - final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) { + final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) { mOomAdjuster.updateOomAdjLocked(oomAdjReason); } @@ -16227,8 +16237,7 @@ public class ActivityManagerService extends IActivityManager.Stub * @return whether updateOomAdjLocked(app) was successful. */ @GuardedBy("this") - final boolean updateOomAdjLocked( - ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) { + final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) { return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason); } @@ -16461,16 +16470,14 @@ public class ActivityManagerService extends IActivityManager.Stub mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist); } - private void trimApplications( - boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) { + private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) { synchronized (this) { trimApplicationsLocked(forceFullOomAdj, oomAdjReason); } } @GuardedBy("this") - private void trimApplicationsLocked( - boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) { + private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) { // First remove any unused application processes whose package // has been removed. boolean didSomething = false; @@ -17442,7 +17449,7 @@ public class ActivityManagerService extends IActivityManager.Stub } pr.mState.setHasOverlayUi(hasOverlayUi); //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid); - updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY); } } @@ -17577,7 +17584,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void trimApplications() { - ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY); + ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY); } public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) { @@ -17626,9 +17633,9 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void updateOomAdj() { + public void updateOomAdj(@OomAdjReason int oomAdjReason) { synchronized (ActivityManagerService.this) { - ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + ActivityManagerService.this.updateOomAdjLocked(oomAdjReason); } } @@ -18288,8 +18295,7 @@ public class ActivityManagerService extends IActivityManager.Stub // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this // workaround can be removed. (b/213288355) if (isNewPending) { - mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, - OomAdjuster.OOM_ADJ_REASON_ACTIVITY); + mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY); } // We need to update the network rules for the app coming to the top state so that // it can access network when the device or the app is in a restricted state diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index c343ec24412a..061bcd740f6b 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -555,6 +555,15 @@ class AppErrors { } } + void sendRecoverableCrashToAppExitInfo( + ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) { + if (r == null || crashInfo == null + || !"Native crash".equals(crashInfo.exceptionClassName)) return; + synchronized (mService) { + mService.mProcessList.noteAppRecoverableCrash(r); + } + } + /** * Bring up the "unexpected error" dialog box for a crashing app. * Deal with edge cases (intercepts from instrumented applications, diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index 44436369fb31..4c0dd115a3cc 100644 --- a/services/core/java/com/android/server/am/AppExitInfoTracker.java +++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java @@ -308,6 +308,16 @@ public final class AppExitInfoTracker { mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget(); } + void scheduleNoteAppRecoverableCrash(final ProcessRecord app) { + if (!mAppExitInfoLoaded.get() || app == null || app.info == null) return; + + ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis()); + raw.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE); + raw.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN); + raw.setDescription("recoverable_crash"); + mKillHandler.obtainMessage(KillHandler.MSG_APP_RECOVERABLE_CRASH, raw).sendToTarget(); + } + void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason, final @SubReason int subReason, final String msg) { if (!mAppExitInfoLoaded.get()) { @@ -421,8 +431,24 @@ public final class AppExitInfoTracker { scheduleLogToStatsdLocked(info, true); } + /** + * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't + * being killed but the crash should still be added to AppExitInfo. Also, because we're not + * crashing, don't log out to statsd. + */ + @VisibleForTesting + @GuardedBy("mLock") + void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) { + addExitInfoLocked(raw, /* recoverable */ true); + } + @GuardedBy("mLock") private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) { + return addExitInfoLocked(raw, /* recoverable */ false); + } + + @GuardedBy("mLock") + private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) { if (!mAppExitInfoLoaded.get()) { Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage"); return null; @@ -438,7 +464,7 @@ public final class AppExitInfoTracker { } } for (int i = 0; i < packages.length; i++) { - addExitInfoInnerLocked(packages[i], uid, info); + addExitInfoInnerLocked(packages[i], uid, info, recoverable); } schedulePersistProcessExitInfo(false); @@ -845,7 +871,8 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") - private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) { + private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info, + boolean recoverable) { AppExitInfoContainer container = mData.get(packageName, uid); if (container == null) { container = new AppExitInfoContainer(mAppExitInfoHistoryListSize); @@ -859,7 +886,11 @@ public final class AppExitInfoTracker { } mData.put(packageName, uid, container); } - container.addExitInfoLocked(info); + if (recoverable) { + container.addRecoverableCrashLocked(info); + } else { + container.addExitInfoLocked(info); + } } @GuardedBy("mLock") @@ -1284,38 +1315,40 @@ public final class AppExitInfoTracker { * A container class of {@link android.app.ApplicationExitInfo} */ final class AppExitInfoContainer { - private SparseArray<ApplicationExitInfo> mInfos; // index is pid + private SparseArray<ApplicationExitInfo> mInfos; // index is a pid + private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid private int mMaxCapacity; private int mUid; // Application uid, not isolated uid. AppExitInfoContainer(final int maxCapacity) { mInfos = new SparseArray<ApplicationExitInfo>(); + mRecoverableCrashes = new SparseArray<ApplicationExitInfo>(); mMaxCapacity = maxCapacity; } @GuardedBy("mLock") - void getExitInfoLocked(final int filterPid, final int maxNum, - ArrayList<ApplicationExitInfo> results) { + void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid, + final int maxNum, ArrayList<ApplicationExitInfo> results) { if (filterPid > 0) { - ApplicationExitInfo r = mInfos.get(filterPid); + ApplicationExitInfo r = map.get(filterPid); if (r != null) { results.add(r); } } else { - final int numRep = mInfos.size(); + final int numRep = map.size(); if (maxNum <= 0 || numRep <= maxNum) { // Return all records. for (int i = 0; i < numRep; i++) { - results.add(mInfos.valueAt(i)); + results.add(map.valueAt(i)); } Collections.sort(results, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); } else { if (maxNum == 1) { // Most of the caller might be only interested with the most recent one - ApplicationExitInfo r = mInfos.valueAt(0); + ApplicationExitInfo r = map.valueAt(0); for (int i = 1; i < numRep; i++) { - ApplicationExitInfo t = mInfos.valueAt(i); + ApplicationExitInfo t = map.valueAt(i); if (r.getTimestamp() < t.getTimestamp()) { r = t; } @@ -1326,7 +1359,7 @@ public final class AppExitInfoTracker { ArrayList<ApplicationExitInfo> list = mTmpInfoList2; list.clear(); for (int i = 0; i < numRep; i++) { - list.add(mInfos.valueAt(i)); + list.add(map.valueAt(i)); } Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); @@ -1340,24 +1373,30 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") - void addExitInfoLocked(ApplicationExitInfo info) { + void getExitInfoLocked(final int filterPid, final int maxNum, + ArrayList<ApplicationExitInfo> results) { + getInfosLocked(mInfos, filterPid, maxNum, results); + } + + @GuardedBy("mLock") + void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) { int size; - if ((size = mInfos.size()) >= mMaxCapacity) { + if ((size = map.size()) >= mMaxCapacity) { int oldestIndex = -1; long oldestTimeStamp = Long.MAX_VALUE; for (int i = 0; i < size; i++) { - ApplicationExitInfo r = mInfos.valueAt(i); + ApplicationExitInfo r = map.valueAt(i); if (r.getTimestamp() < oldestTimeStamp) { oldestTimeStamp = r.getTimestamp(); oldestIndex = i; } } if (oldestIndex >= 0) { - final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile(); + final File traceFile = map.valueAt(oldestIndex).getTraceFile(); if (traceFile != null) { traceFile.delete(); } - mInfos.removeAt(oldestIndex); + map.removeAt(oldestIndex); } } // Claim the state information if there is any @@ -1367,7 +1406,17 @@ public final class AppExitInfoTracker { mActiveAppStateSummary, uid, pid)); info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid)); info.setAppTraceRetriever(mAppTraceRetriever); - mInfos.append(pid, info); + map.append(pid, info); + } + + @GuardedBy("mLock") + void addExitInfoLocked(ApplicationExitInfo info) { + addInfoLocked(mInfos, info); + } + + @GuardedBy("mLock") + void addRecoverableCrashLocked(ApplicationExitInfo info) { + addInfoLocked(mRecoverableCrashes, info); } @GuardedBy("mLock") @@ -1382,9 +1431,9 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") - void destroyLocked() { - for (int i = mInfos.size() - 1; i >= 0; i--) { - ApplicationExitInfo ai = mInfos.valueAt(i); + void destroyLocked(SparseArray<ApplicationExitInfo> map) { + for (int i = map.size() - 1; i >= 0; i--) { + ApplicationExitInfo ai = map.valueAt(i); final File traceFile = ai.getTraceFile(); if (traceFile != null) { traceFile.delete(); @@ -1395,24 +1444,37 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") + void destroyLocked() { + destroyLocked(mInfos); + destroyLocked(mRecoverableCrashes); + } + + @GuardedBy("mLock") void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) { - if (callback != null) { - for (int i = mInfos.size() - 1; i >= 0; i--) { - switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) { - case FOREACH_ACTION_REMOVE_ITEM: - final File traceFile = mInfos.valueAt(i).getTraceFile(); - if (traceFile != null) { - traceFile.delete(); - } - mInfos.removeAt(i); - break; - case FOREACH_ACTION_STOP_ITERATION: - i = 0; - break; - case FOREACH_ACTION_NONE: - default: - break; - } + if (callback == null) return; + for (int i = mInfos.size() - 1; i >= 0; i--) { + switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) { + case FOREACH_ACTION_STOP_ITERATION: return; + case FOREACH_ACTION_REMOVE_ITEM: + final File traceFile = mInfos.valueAt(i).getTraceFile(); + if (traceFile != null) { + traceFile.delete(); + } + mInfos.removeAt(i); + break; + } + } + for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { + switch (callback.apply( + mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) { + case FOREACH_ACTION_STOP_ITERATION: return; + case FOREACH_ACTION_REMOVE_ITEM: + final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile(); + if (traceFile != null) { + traceFile.delete(); + } + mRecoverableCrashes.removeAt(i); + break; } } } @@ -1423,6 +1485,9 @@ public final class AppExitInfoTracker { for (int i = mInfos.size() - 1; i >= 0; i--) { list.add(mInfos.valueAt(i)); } + for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { + list.add(mRecoverableCrashes.valueAt(i)); + } Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); int size = list.size(); for (int i = 0; i < size; i++) { @@ -1434,10 +1499,13 @@ public final class AppExitInfoTracker { void writeToProto(ProtoOutputStream proto, long fieldId) { long token = proto.start(fieldId); proto.write(AppsExitInfoProto.Package.User.UID, mUid); - int size = mInfos.size(); - for (int i = 0; i < size; i++) { + for (int i = 0; i < mInfos.size(); i++) { mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); } + for (int i = 0; i < mRecoverableCrashes.size(); i++) { + mRecoverableCrashes.valueAt(i).writeToProto( + proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH); + } proto.end(token); } @@ -1448,14 +1516,23 @@ public final class AppExitInfoTracker { next != ProtoInputStream.NO_MORE_FIELDS; next = proto.nextField()) { switch (next) { - case (int) AppsExitInfoProto.Package.User.UID: + case (int) AppsExitInfoProto.Package.User.UID: { mUid = proto.readInt(AppsExitInfoProto.Package.User.UID); break; - case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: + } + case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: { ApplicationExitInfo info = new ApplicationExitInfo(); info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); mInfos.put(info.getPid(), info); break; + } + case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: { + ApplicationExitInfo info = new ApplicationExitInfo(); + info.readFromProto( + proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH); + mRecoverableCrashes.put(info.getPid(), info); + break; + } } } proto.end(token); @@ -1472,6 +1549,11 @@ public final class AppExitInfoTracker { list.add(mInfos.valueAt(i)); } } + for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { + if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) { + list.add(mRecoverableCrashes.valueAt(i)); + } + } return list; } } @@ -1610,6 +1692,7 @@ public final class AppExitInfoTracker { static final int MSG_PROC_DIED = 4103; static final int MSG_APP_KILL = 4104; static final int MSG_STATSD_LOG = 4105; + static final int MSG_APP_RECOVERABLE_CRASH = 4106; KillHandler(Looper looper) { super(looper, null, true); @@ -1648,6 +1731,14 @@ public final class AppExitInfoTracker { } } break; + case MSG_APP_RECOVERABLE_CRASH: { + ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj; + synchronized (mLock) { + handleNoteAppRecoverableCrashLocked(raw); + } + recycleRawRecord(raw); + } + break; default: super.handleMessage(msg); } diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index bfc8251d97bb..9e61ce405ca7 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -18,7 +18,6 @@ package com.android.server.am; import static com.android.internal.util.Preconditions.checkState; import static com.android.server.am.BroadcastRecord.deliveryStateToString; -import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; import static com.android.server.am.BroadcastRecord.isReceiverEquals; import android.annotation.IntDef; @@ -709,7 +708,7 @@ class BroadcastProcessQueue { || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit); final boolean isLPQueueEligible = shouldConsiderLPQueue && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime - && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex); + && !nextLPRecord.isBlocked(nextLPRecordIndex); return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue; } @@ -912,39 +911,20 @@ class BroadcastProcessQueue { } } - private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) { - final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index]; - - int existingDeferredCount = 0; - if (r.deferUntilActive) { - for (int i = 0; i < index; i++) { - if (r.deferredUntilActive[i]) existingDeferredCount++; - } - } - - // We might be blocked waiting for other receivers to finish, - // typically for an ordered broadcast or priority traunches - if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount - && !isDeliveryStateTerminal(r.getDeliveryState(index))) { - return true; - } - return false; - } - /** * Update {@link #getRunnableAt()} if it's currently invalidated. */ private void updateRunnableAt() { - final SomeArgs next = peekNextBroadcast(); + if (!mRunnableAtInvalidated) return; mRunnableAtInvalidated = false; + + final SomeArgs next = peekNextBroadcast(); if (next != null) { final BroadcastRecord r = (BroadcastRecord) next.arg1; final int index = next.argi1; final long runnableAt = r.enqueueTime; - // If we're specifically queued behind other ordered dispatch activity, - // we aren't runnable yet - if (blockedOnOrderedDispatch(r, index)) { + if (r.isBlocked(index)) { mRunnableAt = Long.MAX_VALUE; mRunnableAtReason = REASON_BLOCKED; return; @@ -1262,12 +1242,12 @@ class BroadcastProcessQueue { pw.print(info.activityInfo.name); } pw.println(); - final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex]; - if (blockedUntilTerminalCount != -1) { + final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex]; + if (blockedUntilBeyondCount != -1) { pw.print(" blocked until "); - pw.print(blockedUntilTerminalCount); + pw.print(blockedUntilBeyondCount); pw.print(", currently at "); - pw.print(record.terminalCount); + pw.print(record.beyondCount); pw.print(" of "); pw.println(record.receivers.size()); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index bd36c3ff6f98..5a4d315767ca 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -17,6 +17,7 @@ package com.android.server.am; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE; import static android.text.TextUtils.formatSimple; @@ -37,7 +38,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_L import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU; -import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index a4bdf61e628f..e532c15addd0 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE; @@ -38,7 +39,6 @@ import static com.android.server.am.BroadcastRecord.getReceiverPackageName; import static com.android.server.am.BroadcastRecord.getReceiverProcessName; import static com.android.server.am.BroadcastRecord.getReceiverUid; import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; -import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1008,6 +1008,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } final BroadcastRecord r = queue.getActive(); + final int index = queue.getActiveIndex(); if (r.ordered) { r.resultCode = resultCode; r.resultData = resultData; @@ -1015,18 +1016,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (!r.isNoAbort()) { r.resultAbort = resultAbort; } + } - // When the caller aborted an ordered broadcast, we mark all - // remaining receivers as skipped - if (r.resultAbort) { - for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) { - setDeliveryState(null, null, r, i, r.receivers.get(i), - BroadcastRecord.DELIVERY_SKIPPED, "resultAbort"); - } + // To ensure that "beyond" high-water marks are updated in a monotonic + // way, we finish this receiver before possibly skipping any remaining + // aborted receivers + final boolean res = finishReceiverActiveLocked(queue, + BroadcastRecord.DELIVERY_DELIVERED, "remote app"); + + // When the caller aborted an ordered broadcast, we mark all + // remaining receivers as skipped + if (r.resultAbort) { + for (int i = index + 1; i < r.receivers.size(); i++) { + setDeliveryState(null, null, r, i, r.receivers.get(i), + BroadcastRecord.DELIVERY_SKIPPED, "resultAbort"); } } - return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app"); + return res; } /** @@ -1108,21 +1115,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @NonNull Object receiver, @DeliveryState int newDeliveryState, @NonNull String reason) { final int cookie = traceBegin("setDeliveryState"); + + // Remember the old state and apply the new state final int oldDeliveryState = getDeliveryState(r, index); - boolean checkFinished = false; - - // Only apply state when we haven't already reached a terminal state; - // this is how we ignore racing timeout messages - if (!isDeliveryStateTerminal(oldDeliveryState)) { - r.setDeliveryState(index, newDeliveryState, reason); - if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) { - r.deferredCount--; - } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) { - // If we're deferring a broadcast, maybe that's enough to unblock the final callback - r.deferredCount++; - checkFinished = true; - } - } + final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason); // Emit any relevant tracing results when we're changing the delivery // state as part of running from a queue @@ -1147,15 +1143,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { + deliveryStateToString(newDeliveryState) + " because " + reason); } - r.terminalCount++; notifyFinishReceiver(queue, app, r, index, receiver); - checkFinished = true; } - // When entire ordered broadcast finished, deliver final result - if (checkFinished) { - final boolean recordFinished = - ((r.terminalCount + r.deferredCount) == r.receivers.size()); - if (recordFinished) { + + // When we've reached a new high-water mark, we might be in a position + // to unblock other receivers or the final resultTo + if (beyondCountChanged) { + if (r.beyondCount == r.receivers.size()) { scheduleResultTo(r); } diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index c368290386a0..64fe39314f0e 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -24,6 +24,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; +import android.annotation.CheckResult; import android.annotation.CurrentTimeMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; @@ -101,8 +102,7 @@ final class BroadcastRecord extends Binder { final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo final @DeliveryState int[] delivery; // delivery state of each receiver final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver - final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred - final int[] blockedUntilTerminalCount; // blocked until count of each receiver + final int[] blockedUntilBeyondCount; // blocked until count of each receiver @Nullable ProcessRecord resultToApp; // who receives final result if non-null @Nullable IIntentReceiver resultTo; // who receives final result if non-null boolean deferred; @@ -134,6 +134,7 @@ final class BroadcastRecord extends Binder { int manifestSkipCount; // number of manifest receivers skipped. int terminalCount; // number of receivers in terminal state. int deferredCount; // number of receivers in deferred state. + int beyondCount; // high-water number of receivers we've moved beyond. @Nullable BroadcastQueue queue; // the outbound queue handling this broadcast // Determines the privileges the app's process has in regard to background starts. @@ -219,6 +220,23 @@ final class BroadcastRecord extends Binder { } } + /** + * Return if the given delivery state is "beyond", which means that we've + * moved beyond this receiver, and future receivers are now unblocked. + */ + static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) { + switch (deliveryState) { + case DELIVERY_DELIVERED: + case DELIVERY_SKIPPED: + case DELIVERY_TIMEOUT: + case DELIVERY_FAILURE: + case DELIVERY_DEFERRED: + return true; + default: + return false; + } + } + ProcessRecord curApp; // hosting application of current receiver. ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // the manifest receiver that is currently running. @@ -356,7 +374,7 @@ final class BroadcastRecord extends Binder { TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw); pw.print(' '); } - pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") "); + pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") "); pw.print("#"); pw.print(i); pw.print(": "); if (o instanceof BroadcastFilter) { pw.println(o); @@ -411,8 +429,7 @@ final class BroadcastRecord extends Binder { urgent = calculateUrgent(_intent, _options); deferUntilActive = calculateDeferUntilActive(_callingUid, _options, _resultTo, _serialized, urgent); - deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0]; - blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized); + blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized); scheduledTime = new long[delivery.length]; terminalTime = new long[delivery.length]; resultToApp = _resultToApp; @@ -423,7 +440,7 @@ final class BroadcastRecord extends Binder { ordered = _serialized; sticky = _sticky; initialSticky = _initialSticky; - prioritized = isPrioritized(blockedUntilTerminalCount, _serialized); + prioritized = isPrioritized(blockedUntilBeyondCount, _serialized); userId = _userId; nextReceiver = 0; state = IDLE; @@ -467,8 +484,7 @@ final class BroadcastRecord extends Binder { delivery = from.delivery; deliveryReasons = from.deliveryReasons; deferUntilActive = from.deferUntilActive; - deferredUntilActive = from.deferredUntilActive; - blockedUntilTerminalCount = from.blockedUntilTerminalCount; + blockedUntilBeyondCount = from.blockedUntilBeyondCount; scheduledTime = from.scheduledTime; terminalTime = from.terminalTime; resultToApp = from.resultToApp; @@ -627,32 +643,72 @@ final class BroadcastRecord extends Binder { /** * Update the delivery state of the given {@link #receivers} index. * Automatically updates any time measurements related to state changes. + * + * @return if {@link #beyondCount} changed due to this state transition, + * indicating that other events may be unblocked. */ - void setDeliveryState(int index, @DeliveryState int deliveryState, + @CheckResult + boolean setDeliveryState(int index, @DeliveryState int newDeliveryState, @NonNull String reason) { - delivery[index] = deliveryState; - deliveryReasons[index] = reason; - if (deferUntilActive) deferredUntilActive[index] = false; - switch (deliveryState) { - case DELIVERY_DELIVERED: - case DELIVERY_SKIPPED: - case DELIVERY_TIMEOUT: - case DELIVERY_FAILURE: - terminalTime[index] = SystemClock.uptimeMillis(); + final int oldDeliveryState = delivery[index]; + if (isDeliveryStateTerminal(oldDeliveryState) + || newDeliveryState == oldDeliveryState) { + // We've already arrived in terminal or requested state, so leave + // any statistics and reasons intact from the first transition + return false; + } + + switch (oldDeliveryState) { + case DELIVERY_DEFERRED: + deferredCount--; break; + } + switch (newDeliveryState) { case DELIVERY_SCHEDULED: scheduledTime[index] = SystemClock.uptimeMillis(); break; case DELIVERY_DEFERRED: - if (deferUntilActive) deferredUntilActive[index] = true; + deferredCount++; + break; + case DELIVERY_DELIVERED: + case DELIVERY_SKIPPED: + case DELIVERY_TIMEOUT: + case DELIVERY_FAILURE: + terminalTime[index] = SystemClock.uptimeMillis(); + terminalCount++; break; } + + delivery[index] = newDeliveryState; + deliveryReasons[index] = reason; + + // If this state change might bring us to a new high-water mark, bring + // ourselves as high as we possibly can + final int oldBeyondCount = beyondCount; + if (index >= beyondCount) { + for (int i = beyondCount; i < delivery.length; i++) { + if (isDeliveryStateBeyond(getDeliveryState(i))) { + beyondCount = i + 1; + } else { + break; + } + } + } + return (beyondCount != oldBeyondCount); } @DeliveryState int getDeliveryState(int index) { return delivery[index]; } + /** + * @return if the given {@link #receivers} index should be considered + * blocked based on the current status of the overall broadcast. + */ + boolean isBlocked(int index) { + return (beyondCount < blockedUntilBeyondCount[index]); + } + boolean wasDeliveryAttempted(int index) { final int deliveryState = getDeliveryState(index); switch (deliveryState) { @@ -757,36 +813,36 @@ final class BroadcastRecord extends Binder { * has prioritized tranches of receivers. */ @VisibleForTesting - static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount, + static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount, boolean ordered) { - return !ordered && (blockedUntilTerminalCount.length > 0) - && (blockedUntilTerminalCount[0] != -1); + return !ordered && (blockedUntilBeyondCount.length > 0) + && (blockedUntilBeyondCount[0] != -1); } /** - * Calculate the {@link #terminalCount} that each receiver should be + * Calculate the {@link #beyondCount} that each receiver should be * considered blocked until. * <p> * For example, in an ordered broadcast, receiver {@code N} is blocked until - * receiver {@code N-1} reaches a terminal state. Similarly, in a - * prioritized broadcast, receiver {@code N} is blocked until all receivers - * of a higher priority reach a terminal state. + * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in + * a prioritized broadcast, receiver {@code N} is blocked until all + * receivers of a higher priority reach a terminal or deferred state. * <p> - * When there are no terminal count constraints, the blocked value for each + * When there are no beyond count constraints, the blocked value for each * receiver is {@code -1}. */ @VisibleForTesting - static @NonNull int[] calculateBlockedUntilTerminalCount( + static @NonNull int[] calculateBlockedUntilBeyondCount( @NonNull List<Object> receivers, boolean ordered) { final int N = receivers.size(); - final int[] blockedUntilTerminalCount = new int[N]; + final int[] blockedUntilBeyondCount = new int[N]; int lastPriority = 0; int lastPriorityIndex = 0; for (int i = 0; i < N; i++) { if (ordered) { // When sending an ordered broadcast, we need to block this // receiver until all previous receivers have terminated - blockedUntilTerminalCount[i] = i; + blockedUntilBeyondCount[i] = i; } else { // When sending a prioritized broadcast, we only need to wait // for the previous tranche of receivers to be terminated @@ -794,18 +850,18 @@ final class BroadcastRecord extends Binder { if ((i == 0) || (thisPriority != lastPriority)) { lastPriority = thisPriority; lastPriorityIndex = i; - blockedUntilTerminalCount[i] = i; + blockedUntilBeyondCount[i] = i; } else { - blockedUntilTerminalCount[i] = lastPriorityIndex; + blockedUntilBeyondCount[i] = lastPriorityIndex; } } } // If the entire list is in the same priority tranche, mark as -1 to // indicate that none of them need to wait - if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) { - Arrays.fill(blockedUntilTerminalCount, -1); + if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) { + Arrays.fill(blockedUntilBeyondCount, -1); } - return blockedUntilTerminalCount; + return blockedUntilBeyondCount; } static int getReceiverUid(@NonNull Object receiver) { diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 568997bb2667..f42087ff8006 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -18,6 +18,28 @@ package com.android.server.am; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE; import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION; @@ -26,6 +48,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import android.annotation.IntDef; import android.app.ActivityManager; +import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityThread; import android.app.ApplicationExitInfo; import android.app.IApplicationThread; @@ -139,6 +162,26 @@ public final class CachedAppOptimizer { FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS; static final int UNFREEZE_REASON_FEATURE_FLAGS = FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS; + static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT; + static final int UNFREEZE_REASON_SYSTEM_INIT = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT; + static final int UNFREEZE_REASON_BACKUP = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP; + static final int UNFREEZE_REASON_SHELL = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL; + static final int UNFREEZE_REASON_REMOVE_TASK = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK; + static final int UNFREEZE_REASON_UID_IDLE = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE; + static final int UNFREEZE_REASON_STOP_SERVICE = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE; + static final int UNFREEZE_REASON_EXECUTING_SERVICE = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE; + static final int UNFREEZE_REASON_RESTRICTION_CHANGE = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE; + static final int UNFREEZE_REASON_COMPONENT_DISABLED = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED; @IntDef(prefix = {"UNFREEZE_REASON_"}, value = { UNFREEZE_REASON_NONE, @@ -160,6 +203,16 @@ public final class CachedAppOptimizer { UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE, UNFREEZE_REASON_BINDER_TXNS, UNFREEZE_REASON_FEATURE_FLAGS, + UNFREEZE_REASON_SHORT_FGS_TIMEOUT, + UNFREEZE_REASON_SYSTEM_INIT, + UNFREEZE_REASON_BACKUP, + UNFREEZE_REASON_SHELL, + UNFREEZE_REASON_REMOVE_TASK, + UNFREEZE_REASON_UID_IDLE, + UNFREEZE_REASON_STOP_SERVICE, + UNFREEZE_REASON_EXECUTING_SERVICE, + UNFREEZE_REASON_RESTRICTION_CHANGE, + UNFREEZE_REASON_COMPONENT_DISABLED, }) @Retention(RetentionPolicy.SOURCE) public @interface UnfreezeReason {} @@ -1329,7 +1382,7 @@ public final class CachedAppOptimizer { } try { - traceAppFreeze(app.processName, pid, false); + traceAppFreeze(app.processName, pid, reason); Process.setProcessFrozen(pid, app.uid, false); opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis()); @@ -1341,7 +1394,7 @@ public final class CachedAppOptimizer { } if (!opt.isFrozen()) { - Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName); + Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName + " for " + reason); mFreezeHandler.sendMessage( mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG, @@ -1365,13 +1418,13 @@ public final class CachedAppOptimizer { * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app. * @param pid pid of the process to be unfrozen */ - void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) { + void unfreezeProcess(int pid, @OomAdjReason int reason) { synchronized (mFreezerLock) { ProcessRecord app = mFrozenProcesses.get(pid); if (app == null) { return; } - Slog.d(TAG_AM, "quick sync unfreeze " + pid); + Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " + reason); try { freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS); } catch (RuntimeException e) { @@ -1380,7 +1433,7 @@ public final class CachedAppOptimizer { } try { - traceAppFreeze(app.processName, pid, false); + traceAppFreeze(app.processName, pid, reason); Process.setProcessFrozen(pid, app.uid, false); } catch (Exception e) { Slog.e(TAG_AM, "Unable to quick unfreeze " + pid); @@ -1388,9 +1441,15 @@ public final class CachedAppOptimizer { } } - private static void traceAppFreeze(String processName, int pid, boolean freeze) { + /** + * Trace app freeze status + * @param processName The name of the target process + * @param pid The pid of the target process + * @param reason UNFREEZE_REASON_XXX (>=0) for unfreezing and -1 for freezing + */ + private static void traceAppFreeze(String processName, int pid, int reason) { Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK, - (freeze ? "Freeze " : "Unfreeze ") + processName + ":" + pid); + (reason < 0 ? "Freeze " : "Unfreeze ") + processName + ":" + pid + " " + reason); } /** @@ -1540,12 +1599,12 @@ public final class CachedAppOptimizer { public long mOrigAnonRss; public int mProcState; public int mOomAdj; - public @OomAdjuster.OomAdjReason int mOomAdjReason; + public @OomAdjReason int mOomAdjReason; SingleCompactionStats(long[] rss, CompactSource source, String processName, long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss, long cpuTimeMillis, int procState, int oomAdj, - @OomAdjuster.OomAdjReason int oomAdjReason, int uid) { + @OomAdjReason int oomAdjReason, int uid) { mRssAfterCompaction = rss; mSourceType = source; mProcessName = processName; @@ -2063,7 +2122,7 @@ public final class CachedAppOptimizer { long unfreezeTime = opt.getFreezeUnfreezeTime(); try { - traceAppFreeze(proc.processName, pid, true); + traceAppFreeze(proc.processName, pid, -1); Process.setProcessFrozen(pid, proc.uid, true); opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis()); @@ -2127,7 +2186,7 @@ public final class CachedAppOptimizer { private void reportUnfreeze(int pid, int frozenDuration, String processName, @UnfreezeReason int reason) { - EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName); + EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason); // See above for why we're not taking mPhenotypeFlagLock here if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { @@ -2201,32 +2260,52 @@ public final class CachedAppOptimizer { } } - static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) { + static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) { switch (oomAdjReason) { - case OomAdjuster.OOM_ADJ_REASON_ACTIVITY: + case OOM_ADJ_REASON_ACTIVITY: return UNFREEZE_REASON_ACTIVITY; - case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER: + case OOM_ADJ_REASON_FINISH_RECEIVER: return UNFREEZE_REASON_FINISH_RECEIVER; - case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER: + case OOM_ADJ_REASON_START_RECEIVER: return UNFREEZE_REASON_START_RECEIVER; - case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE: + case OOM_ADJ_REASON_BIND_SERVICE: return UNFREEZE_REASON_BIND_SERVICE; - case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE: + case OOM_ADJ_REASON_UNBIND_SERVICE: return UNFREEZE_REASON_UNBIND_SERVICE; - case OomAdjuster.OOM_ADJ_REASON_START_SERVICE: + case OOM_ADJ_REASON_START_SERVICE: return UNFREEZE_REASON_START_SERVICE; - case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER: + case OOM_ADJ_REASON_GET_PROVIDER: return UNFREEZE_REASON_GET_PROVIDER; - case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER: + case OOM_ADJ_REASON_REMOVE_PROVIDER: return UNFREEZE_REASON_REMOVE_PROVIDER; - case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY: + case OOM_ADJ_REASON_UI_VISIBILITY: return UNFREEZE_REASON_UI_VISIBILITY; - case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST: + case OOM_ADJ_REASON_ALLOWLIST: return UNFREEZE_REASON_ALLOWLIST; - case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN: + case OOM_ADJ_REASON_PROCESS_BEGIN: return UNFREEZE_REASON_PROCESS_BEGIN; - case OomAdjuster.OOM_ADJ_REASON_PROCESS_END: + case OOM_ADJ_REASON_PROCESS_END: return UNFREEZE_REASON_PROCESS_END; + case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: + return UNFREEZE_REASON_SHORT_FGS_TIMEOUT; + case OOM_ADJ_REASON_SYSTEM_INIT: + return UNFREEZE_REASON_SYSTEM_INIT; + case OOM_ADJ_REASON_BACKUP: + return UNFREEZE_REASON_BACKUP; + case OOM_ADJ_REASON_SHELL: + return UNFREEZE_REASON_SHELL; + case OOM_ADJ_REASON_REMOVE_TASK: + return UNFREEZE_REASON_REMOVE_TASK; + case OOM_ADJ_REASON_UID_IDLE: + return UNFREEZE_REASON_UID_IDLE; + case OOM_ADJ_REASON_STOP_SERVICE: + return UNFREEZE_REASON_STOP_SERVICE; + case OOM_ADJ_REASON_EXECUTING_SERVICE: + return UNFREEZE_REASON_EXECUTING_SERVICE; + case OOM_ADJ_REASON_RESTRICTION_CHANGE: + return UNFREEZE_REASON_RESTRICTION_CHANGE; + case OOM_ADJ_REASON_COMPONENT_DISABLED: + return UNFREEZE_REASON_COMPONENT_DISABLED; default: return UNFREEZE_REASON_NONE; } diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index a1fcd424f8c1..d8cb094caa65 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -16,6 +16,8 @@ package com.android.server.am; import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile; import static android.os.Process.PROC_CHAR; import static android.os.Process.PROC_OUT_LONG; @@ -292,7 +294,7 @@ public class ContentProviderHelper { checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); final int verifiedAdj = cpr.proc.mState.getVerifiedAdj(); boolean success = mService.updateOomAdjLocked(cpr.proc, - OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER); + OOM_ADJ_REASON_GET_PROVIDER); // XXX things have changed so updateOomAdjLocked doesn't actually tell us // if the process has been successfully adjusted. So to reduce races with // it, we will check whether the process still exists. Note that this doesn't @@ -757,7 +759,7 @@ public class ContentProviderHelper { // update the app's oom adj value and each provider's usage stats if (providersPublished) { - mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER); + mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER); for (int i = 0, size = providers.size(); i < size; i++) { ContentProviderHolder src = providers.get(i); if (src == null || src.info == null || src.provider == null) { @@ -835,8 +837,7 @@ public class ContentProviderHelper { ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId); if (localCpr.hasExternalProcessHandles()) { if (localCpr.removeExternalProcessHandleLocked(token)) { - mService.updateOomAdjLocked(localCpr.proc, - OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER); + mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER); } else { Slog.e(TAG, "Attempt to remove content provider " + localCpr + " with no external reference for token: " + token + "."); @@ -1506,8 +1507,7 @@ public class ContentProviderHelper { mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName); if (updateOomAdj) { - mService.updateOomAdjLocked(conn.provider.proc, - OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER); + mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER); } } } diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java index 9ff2cd0649d4..727d4df96c47 100644 --- a/services/core/java/com/android/server/am/DropboxRateLimiter.java +++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java @@ -22,7 +22,7 @@ import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; -import com.android.internal.expresslog.Counter; +import com.android.modules.expresslog.Counter; /** Rate limiter for adding errors into dropbox. */ public class DropboxRateLimiter { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index a98571b68067..365dcd9bd785 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -41,6 +41,29 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE; import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; @@ -101,9 +124,9 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; -import android.annotation.IntDef; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityThread; import android.app.AppProtoEnums; import android.app.ApplicationExitInfo; @@ -141,8 +164,6 @@ import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowProcessController; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -154,32 +175,6 @@ import java.util.List; public class OomAdjuster { static final String TAG = "OomAdjuster"; - static final int OOM_ADJ_REASON_NONE = 0; - static final int OOM_ADJ_REASON_ACTIVITY = 1; - static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2; - static final int OOM_ADJ_REASON_START_RECEIVER = 3; - static final int OOM_ADJ_REASON_BIND_SERVICE = 4; - static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5; - static final int OOM_ADJ_REASON_START_SERVICE = 6; - static final int OOM_ADJ_REASON_GET_PROVIDER = 7; - static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8; - static final int OOM_ADJ_REASON_UI_VISIBILITY = 9; - static final int OOM_ADJ_REASON_ALLOWLIST = 10; - static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11; - static final int OOM_ADJ_REASON_PROCESS_END = 12; - static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13; - - @IntDef(prefix = {"OOM_ADJ_REASON_"}, - value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER, - OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE, - OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE, - OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER, - OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST, - OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END, - OOM_ADJ_REASON_SHORT_FGS_TIMEOUT}) - @Retention(RetentionPolicy.SOURCE) - public @interface OomAdjReason {} - public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) { switch (oomReason) { case OOM_ADJ_REASON_NONE: @@ -210,6 +205,24 @@ public class OomAdjuster { return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END; case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; + case OOM_ADJ_REASON_SYSTEM_INIT: + return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT; + case OOM_ADJ_REASON_BACKUP: + return AppProtoEnums.OOM_ADJ_REASON_BACKUP; + case OOM_ADJ_REASON_SHELL: + return AppProtoEnums.OOM_ADJ_REASON_SHELL; + case OOM_ADJ_REASON_REMOVE_TASK: + return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK; + case OOM_ADJ_REASON_UID_IDLE: + return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE; + case OOM_ADJ_REASON_STOP_SERVICE: + return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE; + case OOM_ADJ_REASON_EXECUTING_SERVICE: + return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE; + case OOM_ADJ_REASON_RESTRICTION_CHANGE: + return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE; + case OOM_ADJ_REASON_COMPONENT_DISABLED: + return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED; default: return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO; } @@ -246,6 +259,24 @@ public class OomAdjuster { return OOM_ADJ_REASON_METHOD + "_processEnd"; case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: return OOM_ADJ_REASON_METHOD + "_shortFgs"; + case OOM_ADJ_REASON_SYSTEM_INIT: + return OOM_ADJ_REASON_METHOD + "_systemInit"; + case OOM_ADJ_REASON_BACKUP: + return OOM_ADJ_REASON_METHOD + "_backup"; + case OOM_ADJ_REASON_SHELL: + return OOM_ADJ_REASON_METHOD + "_shell"; + case OOM_ADJ_REASON_REMOVE_TASK: + return OOM_ADJ_REASON_METHOD + "_removeTask"; + case OOM_ADJ_REASON_UID_IDLE: + return OOM_ADJ_REASON_METHOD + "_uidIdle"; + case OOM_ADJ_REASON_STOP_SERVICE: + return OOM_ADJ_REASON_METHOD + "_stopService"; + case OOM_ADJ_REASON_EXECUTING_SERVICE: + return OOM_ADJ_REASON_METHOD + "_executingService"; + case OOM_ADJ_REASON_RESTRICTION_CHANGE: + return OOM_ADJ_REASON_METHOD + "_restrictionChange"; + case OOM_ADJ_REASON_COMPONENT_DISABLED: + return OOM_ADJ_REASON_METHOD + "_componentDisabled"; default: return "_unknown"; } @@ -874,8 +905,7 @@ public class OomAdjuster { } @GuardedBy("mService") - private void performUpdateOomAdjPendingTargetsLocked( - @OomAdjuster.OomAdjReason int oomAdjReason) { + private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) { final ProcessRecord topApp = mService.getTopApp(); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); @@ -3453,7 +3483,7 @@ public class OomAdjuster { } @GuardedBy("mService") - void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) { + void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) { if (!mCachedAppOptimizer.useFreezer()) { return; } diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index 24cc5337b86f..f2331072ce51 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -16,6 +16,8 @@ package com.android.server.am; +import android.app.ActivityManagerInternal.OomAdjReason; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -51,7 +53,7 @@ final class ProcessCachedOptimizerRecord { /** * Last oom adjust change reason for this app. */ - @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason; + @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason; /** * The most recent compaction action performed for this app. @@ -139,12 +141,12 @@ final class ProcessCachedOptimizerRecord { } @GuardedBy("mProcLock") - void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) { + void setLastOomAdjChangeReason(@OomAdjReason int reason) { mLastOomAdjChangeReason = reason; } @GuardedBy("mProcLock") - @OomAdjuster.OomAdjReason + @OomAdjReason int getLastOomAdjChangeReason() { return mLastOomAdjChangeReason; } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 70a696c72a9d..ca41f429a0de 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -51,7 +51,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.CompositeRWLock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.expresslog.Counter; +import com.android.modules.expresslog.Counter; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.anr.AnrLatencyTracker; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b1322ef510d5..312f98ad6e09 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -19,6 +19,8 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; import static android.app.ActivityThread.PROC_START_SEQ_IDENT; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; @@ -2875,7 +2877,7 @@ public final class ProcessList { reasonCode, subReason, reason, !doFreeze /* async */); } killAppZygotesLocked(packageName, appId, userId, false /* force */); - mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END); + mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END); if (doFreeze) { freezePackageCgroup(packageUID, false); } @@ -5140,7 +5142,7 @@ public final class ProcessList { } }); /* Will be a no-op if nothing pending */ - mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE); } } @@ -5203,6 +5205,17 @@ public final class ProcessList { } /** + * Called by ActivityManagerService when a recoverable native crash occurs. + */ + @GuardedBy("mService") + void noteAppRecoverableCrash(final ProcessRecord app) { + if (DEBUG_PROCESSES) { + Slog.i(TAG, "note: " + app + " has a recoverable native crash"); + } + mAppExitInfoTracker.scheduleNoteAppRecoverableCrash(app); + } + + /** * Called by ActivityManagerService when it decides to kill an application process. */ @GuardedBy("mService") diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index afae623cd217..ffb40ee959a4 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; + import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -577,7 +579,9 @@ class ProcessRecord implements WindowProcessListener { processName = _processName; sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage; if (isSdkSandbox) { - sdkSandboxClientAppVolumeUuid = getClientInfoForSdkSandbox().volumeUuid; + final ApplicationInfo clientInfo = getClientInfoForSdkSandbox(); + sdkSandboxClientAppVolumeUuid = clientInfo != null + ? clientInfo.volumeUuid : null; } else { sdkSandboxClientAppVolumeUuid = null; } @@ -1450,7 +1454,7 @@ class ProcessRecord implements WindowProcessListener { } mService.updateLruProcessLocked(this, activityChange, null /* client */); if (updateOomAdj) { - mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY); + mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY); } } } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 71d5d39525b4..ab71acd5f21d 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY; @@ -613,7 +614,7 @@ final class ProcessStateRecord { void forceProcessStateUpTo(int newState) { if (mRepProcState > newState) { synchronized (mProcLock) { - mRepProcState = newState; + setReportedProcState(newState); setCurProcState(newState); setCurRawProcState(newState); } @@ -766,7 +767,7 @@ final class ProcessStateRecord { Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation + " for pid=" + mApp.getPid()); } - mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY); } @GuardedBy({"mService", "mProcLock"}) diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 22e2c9fd889b..8c227f5488d3 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -100,7 +100,6 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, DeviceConfig.NAMESPACE_SWCODEC_NATIVE, - DeviceConfig.NAMESPACE_TETHERING, DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE, DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT, DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 81ba4b813de1..a110169ac8c2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -34,6 +34,7 @@ import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; @@ -42,6 +43,7 @@ import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; +import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED; import static android.app.AppOpsManager.OP_VIBRATE; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; @@ -3027,17 +3029,29 @@ public class AppOpsService extends IAppOpsService.Stub { packageName); } - // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution - // purposes and not as a check, also make sure that the caller is allowed to access - // the data gated by OP_RECORD_AUDIO. + // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and + // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check, + // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO. // // TODO: Revert this change before Android 12. - if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) { - int result = checkOperation(OP_RECORD_AUDIO, uid, packageName); + int result = MODE_DEFAULT; + if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + || code == OP_RECORD_AUDIO_SANDBOXED) { + result = checkOperation(OP_RECORD_AUDIO, uid, packageName); + // Check result if (result != AppOpsManager.MODE_ALLOWED) { return new SyncNotedAppOp(result, code, attributionTag, packageName); } } + // As a special case for OP_CAMERA_SANDBOXED. + if (code == OP_CAMERA_SANDBOXED) { + result = checkOperation(OP_CAMERA, uid, packageName); + // Check result + if (result != AppOpsManager.MODE_ALLOWED) { + return new SyncNotedAppOp(result, code, attributionTag, packageName); + } + } + return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 7c8e6df4acdc..5127d26e6e73 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -19,6 +19,8 @@ package com.android.server.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; @@ -519,6 +521,9 @@ public final class AuthSession implements IBinder.DeathRecipient { try { mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message); + final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR + ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo; + mClientReceiver.onAcquired(aAcquiredInfo, message); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 128ef0b2a802..6c26e2b0ce99 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -413,6 +413,11 @@ public class FingerprintService extends SystemService { Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e); } } + + @Override + public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) { + onAuthenticationAcquired(acquireInfo); + } }; return biometricPrompt.authenticateForOperation( diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index b25206d3b621..7e48f68dcefc 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -3391,6 +3391,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ public void onDefaultNetworkChanged(@NonNull Network network) { + mEventChanges.log("[UnderlyingNW] Default network changed to " + network); Log.d(TAG, "onDefaultNetworkChanged: " + network); // If there is a new default network brought up, cancel the retry task to prevent @@ -3628,6 +3629,7 @@ public class Vpn { mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities) .setTransportInfo(info) .build(); + mEventChanges.log("[VPNRunner] Update agent caps " + mNetworkCapabilities); doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities); } } @@ -3664,6 +3666,7 @@ public class Vpn { private void startIkeSession(@NonNull Network underlyingNetwork) { Log.d(TAG, "Start new IKE session on network " + underlyingNetwork); + mEventChanges.log("[IKE] Start IKE session over " + underlyingNetwork); try { // Clear mInterface to prevent Ikev2VpnRunner being cleared when @@ -3778,6 +3781,7 @@ public class Vpn { } public void onValidationStatus(int status) { + mEventChanges.log("[Validation] validation status " + status); if (status == NetworkAgent.VALIDATION_STATUS_VALID) { // No data stall now. Reset it. mExecutor.execute(() -> { @@ -3818,6 +3822,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ public void onDefaultNetworkLost(@NonNull Network network) { + mEventChanges.log("[UnderlyingNW] Network lost " + network); // If the default network is torn down, there is no need to call // startOrMigrateIkeSession() since it will always check if there is an active network // can be used or not. @@ -3936,6 +3941,8 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ public void onSessionLost(int token, @Nullable Exception exception) { + mEventChanges.log("[IKE] Session lost on network " + mActiveNetwork + + (null == exception ? "" : " reason " + exception.getMessage())); Log.d(TAG, "onSessionLost() called for token " + token); if (!isActiveToken(token)) { @@ -4092,6 +4099,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ private void disconnectVpnRunner() { + mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork); mActiveNetwork = null; mUnderlyingNetworkCapabilities = null; mUnderlyingLinkProperties = null; diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java index 2ac283370886..c039a836843c 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java @@ -334,8 +334,8 @@ public class FontManagerShellCommand extends ShellCommand { } private int installCert(ShellCommand shell) throws SystemFontException { - if (!(Build.IS_USERDEBUG || Build.IS_ENG)) { - throw new SecurityException("Only userdebug/eng device can add debug certificate"); + if (!Build.IS_DEBUGGABLE) { + throw new SecurityException("Only debuggable device can add debug certificate"); } if (Binder.getCallingUid() != Process.ROOT_UID) { throw new SecurityException("Only root can add debug certificate"); diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java index c05a03ee1d2d..c76ca2beda96 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java @@ -286,7 +286,6 @@ final class AdditionalSubtypeUtils { final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder() .setSubtypeNameResId(label) - .setSubtypeNameOverride(untranslatableName) .setPhysicalKeyboardHint( pkLanguageTag == null ? null : new ULocale(pkLanguageTag), pkLayoutType == null ? "" : pkLayoutType) @@ -302,6 +301,9 @@ final class AdditionalSubtypeUtils { if (subtypeId != InputMethodSubtype.SUBTYPE_ID_NONE) { builder.setSubtypeId(subtypeId); } + if (untranslatableName != null) { + builder.setSubtypeNameOverride(untranslatableName); + } tempSubtypesArray.add(builder.build()); } } diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index 43e346a5bfa3..2d4066144a7f 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -323,7 +323,7 @@ public class LocaleManagerService extends SystemService { */ void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId, LocaleList locales) { - String installingPackageName = getInstallingPackageName(appPackageName); + String installingPackageName = getInstallingPackageName(appPackageName, userId); if (installingPackageName != null) { Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED, appPackageName, locales); @@ -464,7 +464,7 @@ public class LocaleManagerService extends SystemService { * Checks if the calling app is the installer of the app whose locale changed. */ private boolean isCallerInstaller(String appPackageName, int userId) { - String installingPackageName = getInstallingPackageName(appPackageName); + String installingPackageName = getInstallingPackageName(appPackageName, userId); if (installingPackageName != null) { // Get the uid of installer-on-record to compare with the calling uid. int installerUid = getPackageUid(installingPackageName, userId); @@ -513,10 +513,11 @@ public class LocaleManagerService extends SystemService { } @Nullable - String getInstallingPackageName(String packageName) { + String getInstallingPackageName(String packageName, int userId) { try { - return mContext.getPackageManager() - .getInstallSourceInfo(packageName).getInstallingPackageName(); + return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ + 0).getPackageManager().getInstallSourceInfo( + packageName).getInstallingPackageName(); } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Package not found " + packageName); } diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java index 215c653f1be7..373d3553e0eb 100644 --- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java +++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java @@ -152,9 +152,10 @@ public class SystemAppUpdateTracker { void onPackageUpdateFinished(String packageName, int uid) { try { if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) { + int userId = UserHandle.getUserId(uid); // If a system app is updated, verify that it has an installer-on-record. String installingPackageName = mLocaleManagerService.getInstallingPackageName( - packageName); + packageName, userId); if (installingPackageName == null) { // We want to broadcast the locales info to the installer. // If this app does not have an installer then do nothing. @@ -162,7 +163,6 @@ public class SystemAppUpdateTracker { } try { - int userId = UserHandle.getUserId(uid); // Fetch the app-specific locales. // If non-empty then send the info to the installer. LocaleList appLocales = mLocaleManagerService.getApplicationLocales( 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 653b71828c5b..5f783742bd26 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -519,17 +519,24 @@ public class ContextHubService extends IContextHubService.Stub { BroadcastReceiver btReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction()) - || BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals( - intent.getAction())) { + if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { sendBtSettingUpdate(/* forceUpdate= */ false); } } }; IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); mContext.registerReceiver(btReceiver, filter); + + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE), + /* notifyForDescendants= */ false, + new ContentObserver(/* handler= */ null) { + @Override + public void onChange(boolean selfChange) { + sendBtSettingUpdate(/* forceUpdate= */ false); + } + }, UserHandle.USER_ALL); } /** diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index f0e8ede5987d..94d5aabe24e5 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -357,12 +357,16 @@ public final class MediaProjectionManagerService extends SystemService } catch (NameNotFoundException e) { throw new IllegalArgumentException("No package matching :" + packageName); } - - projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion, - ai.isPrivilegedApp()); - if (isPermanentGrant) { - mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA, - projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED); + final long callingToken = Binder.clearCallingIdentity(); + try { + projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion, + ai.isPrivilegedApp()); + if (isPermanentGrant) { + mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA, + projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED); + } + } finally { + Binder.restoreCallingIdentity(callingToken); } return projection; } @@ -418,16 +422,9 @@ public final class MediaProjectionManagerService extends SystemService if (packageName == null || packageName.isEmpty()) { throw new IllegalArgumentException("package name must not be empty"); } - MediaProjection projection; final UserHandle callingUser = Binder.getCallingUserHandle(); - final long callingToken = Binder.clearCallingIdentity(); - try { - projection = createProjectionInternal(uid, packageName, type, isPermanentGrant, - callingUser, false); - } finally { - Binder.restoreCallingIdentity(callingToken); - } - return projection; + return createProjectionInternal(uid, packageName, type, isPermanentGrant, + callingUser, false); } @Override // Binder call diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index 273afcc9f769..dff02bf711cd 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -15,44 +15,53 @@ */ package com.android.server.notification; +import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; +import static android.app.Notification.FLAG_AUTO_CANCEL; +import static android.app.Notification.FLAG_GROUP_SUMMARY; +import static android.app.Notification.FLAG_LOCAL_ONLY; +import static android.app.Notification.FLAG_NO_CLEAR; +import static android.app.Notification.FLAG_ONGOING_EVENT; + +import android.annotation.NonNull; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; /** * NotificationManagerService helper for auto-grouping notifications. */ public class GroupHelper { private static final String TAG = "GroupHelper"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); protected static final String AUTOGROUP_KEY = "ranker_group"; + // Flags that all autogroup summaries have + protected static final int BASE_FLAGS = + FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY; + // Flag that autogroup summaries inherits if all children have the flag + private static final int ALL_CHILDREN_FLAG = FLAG_AUTO_CANCEL; + // Flags that autogroup summaries inherits if any child has them + private static final int ANY_CHILDREN_FLAGS = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR; + private final Callback mCallback; private final int mAutoGroupAtCount; - // count the number of ongoing notifications per group - // userId|packageName -> (set of ongoing notifications that aren't in an app group) - final ArrayMap<String, ArraySet<String>> - mOngoingGroupCount = new ArrayMap<>(); - - // Map of user : <Map of package : notification keys>. Only contains notifications that are not - // grouped by the app (aka no group or sort key). - Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>(); + // Only contains notifications that are not explicitly grouped by the app (aka no group or + // sort key). + // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags) + @GuardedBy("mUngroupedNotifications") + private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications + = new ArrayMap<>(); public GroupHelper(int autoGroupAtCount, Callback callback) { mAutoGroupAtCount = autoGroupAtCount; - mCallback = callback; + mCallback = callback; } private String generatePackageKey(int userId, String pkg) { @@ -60,69 +69,30 @@ public class GroupHelper { } @VisibleForTesting - protected int getOngoingGroupCount(int userId, String pkg) { - String key = generatePackageKey(userId, pkg); - return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size(); - } - - private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) { - if (sbn.getNotification().isGroupSummary()) { - return; - } - String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName()); - ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)); - if (add) { - notifications.add(sbn.getKey()); - mOngoingGroupCount.put(key, notifications); - } else { - notifications.remove(sbn.getKey()); - // we don't need to put it back if it is default + @GuardedBy("mUngroupedNotifications") + protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) { + boolean allChildrenHasFlag = children.size() > 0; + int anyChildFlagSet = 0; + for (int i = 0; i < children.size(); i++) { + if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) { + allChildrenHasFlag = false; + } + if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) { + anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS); + } } - - boolean needsOngoingFlag = notifications.size() > 0; - mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag); + return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet; } - public void onNotificationUpdated(StatusBarNotification childSbn) { - updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup()); + private boolean hasAnyFlag(int flags, int mask) { + return (flags & mask) != 0; } public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) { try { - updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup()); - - List<String> notificationsToGroup = new ArrayList<>(); if (!sbn.isAppGroup()) { - // Not grouped by the app, add to the list of notifications for the app; - // send grouping update if app exceeds the autogrouping limit. - synchronized (mUngroupedNotifications) { - Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser - = mUngroupedNotifications.get(sbn.getUserId()); - if (ungroupedNotificationsByUser == null) { - ungroupedNotificationsByUser = new HashMap<>(); - } - mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser); - LinkedHashSet<String> notificationsForPackage - = ungroupedNotificationsByUser.get(sbn.getPackageName()); - if (notificationsForPackage == null) { - notificationsForPackage = new LinkedHashSet<>(); - } - - notificationsForPackage.add(sbn.getKey()); - ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage); - - if (notificationsForPackage.size() >= mAutoGroupAtCount - || autogroupSummaryExists) { - notificationsToGroup.addAll(notificationsForPackage); - } - } - if (notificationsToGroup.size() > 0) { - adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(), - notificationsToGroup.get(0), true); - adjustNotificationBundling(notificationsToGroup, true); - } + maybeGroup(sbn, autogroupSummaryExists); } else { - // Grouped, but not by us. Send updates to un-autogroup, if we grouped it. maybeUngroup(sbn, false, sbn.getUserId()); } @@ -133,7 +103,6 @@ public class GroupHelper { public void onNotificationRemoved(StatusBarNotification sbn) { try { - updateOngoingGroupCount(sbn, false); maybeUngroup(sbn, true, sbn.getUserId()); } catch (Exception e) { Slog.e(TAG, "Error processing canceled notification", e); @@ -141,70 +110,114 @@ public class GroupHelper { } /** - * Un-autogroups notifications that are now grouped by the app. + * A non-app grouped notification has been added or updated + * Evaluate if: + * (a) an existing autogroup summary needs updated flags + * (b) a new autogroup summary needs to be added with correct flags + * (c) other non-app grouped children need to be moved to the autogroup + * + * And stores the list of upgrouped notifications & their flags + */ + private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) { + int flags = 0; + List<String> notificationsToGroup = new ArrayList<>(); + synchronized (mUngroupedNotifications) { + String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName()); + final ArrayMap<String, Integer> children = + mUngroupedNotifications.getOrDefault(key, new ArrayMap<>()); + + children.put(sbn.getKey(), sbn.getNotification().flags); + mUngroupedNotifications.put(key, children); + + if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) { + flags = getAutogroupSummaryFlags(children); + notificationsToGroup.addAll(children.keySet()); + } + } + if (notificationsToGroup.size() > 0) { + if (autogroupSummaryExists) { + mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags); + } else { + mCallback.addAutoGroupSummary( + sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags); + } + for (String key : notificationsToGroup) { + mCallback.addAutoGroup(key); + } + } + } + + /** + * A notification was added that's app grouped, or a notification was removed. + * Evaluate whether: + * (a) an existing autogroup summary needs updated flags + * (b) if we need to remove our autogroup overlay for this notification + * (c) we need to remove the autogroup summary + * + * And updates the internal state of un-app-grouped notifications and their flags */ private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) { - List<String> notificationsToUnAutogroup = new ArrayList<>(); boolean removeSummary = false; + int summaryFlags = 0; + boolean updateSummaryFlags = false; + boolean removeAutogroupOverlay = false; synchronized (mUngroupedNotifications) { - Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser - = mUngroupedNotifications.get(sbn.getUserId()); - if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) { - return; - } - LinkedHashSet<String> notificationsForPackage - = ungroupedNotificationsByUser.get(sbn.getPackageName()); - if (notificationsForPackage == null || notificationsForPackage.size() == 0) { + String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName()); + final ArrayMap<String, Integer> children = + mUngroupedNotifications.getOrDefault(key, new ArrayMap<>()); + if (children.size() == 0) { return; } - if (notificationsForPackage.remove(sbn.getKey())) { - if (!notificationGone) { - // Add the current notification to the ungrouping list if it still exists. - notificationsToUnAutogroup.add(sbn.getKey()); + + // if this notif was autogrouped and now isn't + if (children.containsKey(sbn.getKey())) { + // if this notification was contributing flags that aren't covered by other + // children to the summary, reevaluate flags for the summary + int flags = children.remove(sbn.getKey()); + // this + if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) { + updateSummaryFlags = true; + summaryFlags = getAutogroupSummaryFlags(children); + } + // if this notification still exists and has an autogroup overlay, but is now + // grouped by the app, clear the overlay + if (!notificationGone && sbn.getOverrideGroupKey() != null) { + removeAutogroupOverlay = true; + } + + // If there are no more children left to autogroup, remove the summary + if (children.size() == 0) { + removeSummary = true; } - } - // If the status change of this notification has brought the number of loose - // notifications to zero, remove the summary and un-autogroup. - if (notificationsForPackage.size() == 0) { - ungroupedNotificationsByUser.remove(sbn.getPackageName()); - removeSummary = true; } } if (removeSummary) { - adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false); - } - if (notificationsToUnAutogroup.size() > 0) { - adjustNotificationBundling(notificationsToUnAutogroup, false); - } - } - - private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey, - boolean summaryNeeded) { - if (summaryNeeded) { - mCallback.addAutoGroupSummary(userId, packageName, triggeringKey, - getOngoingGroupCount(userId, packageName) > 0); + mCallback.removeAutoGroupSummary(userId, sbn.getPackageName()); } else { - mCallback.removeAutoGroupSummary(userId, packageName); + if (updateSummaryFlags) { + mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags); + } + } + if (removeAutogroupOverlay) { + mCallback.removeAutoGroup(sbn.getKey()); } } - private void adjustNotificationBundling(List<String> keys, boolean group) { - for (String key : keys) { - if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group); - if (group) { - mCallback.addAutoGroup(key); - } else { - mCallback.removeAutoGroup(key); - } + @VisibleForTesting + int getNotGroupedByAppCount(int userId, String pkg) { + synchronized (mUngroupedNotifications) { + String key = generatePackageKey(userId, pkg); + final ArrayMap<String, Integer> children = + mUngroupedNotifications.getOrDefault(key, new ArrayMap<>()); + return children.size(); } } protected interface Callback { void addAutoGroup(String key); void removeAutoGroup(String key); - void addAutoGroupSummary(int userId, String pkg, String triggeringKey, - boolean needsOngoingFlag); + void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags); void removeAutoGroupSummary(int user, String pkg); - void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag); + void updateAutogroupSummary(int userId, String pkg, int flags); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6d27fe058423..1301cd476c26 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -319,6 +319,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal; +import com.android.server.powerstats.StatsPullAtomCallbackImpl; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.utils.Slogf; @@ -902,11 +903,11 @@ public class NotificationManagerService extends SystemService { * has the same flag. It will delete the flag otherwise * @param userId user id of the autogroup summary * @param pkg package of the autogroup summary - * @param needsOngoingFlag true if the group has at least one ongoing notification + * @param flags the new flags for this summary * @param isAppForeground true if the app is currently in the foreground. */ @GuardedBy("mNotificationLock") - protected void updateAutobundledSummaryFlags(int userId, String pkg, boolean needsOngoingFlag, + protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags, boolean isAppForeground) { ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId); if (summaries == null) { @@ -921,13 +922,8 @@ public class NotificationManagerService extends SystemService { return; } int oldFlags = summary.getSbn().getNotification().flags; - if (needsOngoingFlag) { - summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT; - } else { - summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT; - } - - if (summary.getSbn().getNotification().flags != oldFlags) { + if (oldFlags != flags) { + summary.getSbn().getNotification().flags = flags; mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground, SystemClock.elapsedRealtime())); } @@ -2684,9 +2680,14 @@ public class NotificationManagerService extends SystemService { @Override public void addAutoGroupSummary(int userId, String pkg, String triggeringKey, - boolean needsOngoingFlag) { - NotificationManagerService.this.addAutoGroupSummary( - userId, pkg, triggeringKey, needsOngoingFlag); + int flags) { + NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags); + if (r != null) { + final boolean isAppForeground = + mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; + mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, + SystemClock.elapsedRealtime())); + } } @Override @@ -2697,11 +2698,11 @@ public class NotificationManagerService extends SystemService { } @Override - public void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag) { + public void updateAutogroupSummary(int userId, String pkg, int flags) { boolean isAppForeground = pkg != null && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; synchronized (mNotificationLock) { - updateAutobundledSummaryFlags(userId, pkg, needsOngoingFlag, isAppForeground); + updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground); } } }); @@ -3815,6 +3816,28 @@ public class NotificationManagerService extends SystemService { } @Override + public boolean canUseFullScreenIntent(@NonNull AttributionSource attributionSource) { + final String packageName = attributionSource.getPackageName(); + final int uid = attributionSource.getUid(); + final int userId = UserHandle.getUserId(uid); + checkCallerIsSameApp(packageName, uid, userId); + + final ApplicationInfo applicationInfo; + try { + applicationInfo = mPackageManagerClient.getApplicationInfoAsUser( + packageName, PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e); + return false; + } + final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( + SystemUiSystemPropertiesFlags.NotificationFlags + .SHOW_STICKY_HUN_FOR_DENIED_FSI); + return checkUseFullScreenIntentPermission(attributionSource, applicationInfo, + showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */); + } + + @Override public void updateNotificationChannelGroupForPackage(String pkg, int uid, NotificationChannelGroup group) throws RemoteException { enforceSystemOrSystemUI("Caller not system or systemui"); @@ -5939,19 +5962,6 @@ public class NotificationManagerService extends SystemService { r.addAdjustment(adjustment); } - @VisibleForTesting - void addAutoGroupSummary(int userId, String pkg, String triggeringKey, - boolean needsOngoingFlag) { - NotificationRecord r = createAutoGroupSummary( - userId, pkg, triggeringKey, needsOngoingFlag); - if (r != null) { - final boolean isAppForeground = - mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; - mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, - SystemClock.elapsedRealtime())); - } - } - // Clears the 'fake' auto-group summary. @VisibleForTesting @GuardedBy("mNotificationLock") @@ -5975,7 +5985,7 @@ public class NotificationManagerService extends SystemService { // Creates a 'fake' summary for a package that has exceeded the solo-notification limit. NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey, - boolean needsOngoingFlag) { + int flagsToSet) { NotificationRecord summaryRecord = null; boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId); synchronized (mNotificationLock) { @@ -5985,7 +5995,6 @@ public class NotificationManagerService extends SystemService { // adjustment will post a summary if needed. return null; } - NotificationChannel channel = notificationRecord.getChannel(); final StatusBarNotification adjustedSbn = notificationRecord.getSbn(); userId = adjustedSbn.getUser().getIdentifier(); int uid = adjustedSbn.getUid(); @@ -6008,11 +6017,8 @@ public class NotificationManagerService extends SystemService { .setGroupSummary(true) .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN) .setGroup(GroupHelper.AUTOGROUP_KEY) - .setFlag(FLAG_AUTOGROUP_SUMMARY, true) - .setFlag(Notification.FLAG_GROUP_SUMMARY, true) - .setFlag(FLAG_ONGOING_EVENT, needsOngoingFlag) + .setFlag(flagsToSet, true) .setColor(adjustedSbn.getNotification().color) - .setLocalOnly(true) .build(); summaryNotification.extras.putAll(extras); Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg); @@ -6350,6 +6356,7 @@ public class NotificationManagerService extends SystemService { * The private API only accessible to the system process. */ private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() { + @Override public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId) { @@ -6826,36 +6833,28 @@ public class NotificationManagerService extends SystemService { notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED; - if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) { + if (notification.fullScreenIntent != null) { final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled( SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE); - - final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI); - if (forceDemoteFsiToStickyHun) { makeStickyHun(notification, pkg, userId); - - } else if (showStickyHunIfDenied) { - final AttributionSource source = new AttributionSource.Builder(notificationUid) - .setPackageName(pkg) - .build(); - - final int permissionResult = mPermissionManager.checkPermissionForDataDelivery( - Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null); - - if (permissionResult != PermissionManager.PERMISSION_GRANTED) { - makeStickyHun(notification, pkg, userId); - } - } else { - int fullscreenIntentPermission = getContext().checkPermission( - android.Manifest.permission.USE_FULL_SCREEN_INTENT, -1, notificationUid); - - if (fullscreenIntentPermission != PERMISSION_GRANTED) { - notification.fullScreenIntent = null; - Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the" - + "USE_FULL_SCREEN_INTENT permission"); + final AttributionSource attributionSource = + new AttributionSource.Builder(notificationUid).setPackageName(pkg).build(); + final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( + SystemUiSystemPropertiesFlags.NotificationFlags + .SHOW_STICKY_HUN_FOR_DENIED_FSI); + final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission( + attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */, + true /* forDataDelivery */); + if (!canUseFullScreenIntent) { + if (showStickyHunIfDenied) { + makeStickyHun(notification, pkg, userId); + } else { + notification.fullScreenIntent = null; + Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the" + + "USE_FULL_SCREEN_INTENT permission"); + } } } } @@ -6951,6 +6950,30 @@ public class NotificationManagerService extends SystemService { ai.packageName) == AppOpsManager.MODE_ALLOWED; } + private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource, + @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission, + boolean forDataDelivery) { + if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) { + return true; + } + if (isAppOpPermission) { + final int permissionResult; + if (forDataDelivery) { + permissionResult = mPermissionManager.checkPermissionForDataDelivery( + permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null); + } else { + permissionResult = mPermissionManager.checkPermissionForPreflight( + permission.USE_FULL_SCREEN_INTENT, attributionSource); + } + return permissionResult == PermissionManager.PERMISSION_GRANTED; + } else { + final int permissionResult = getContext().checkPermission( + permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(), + attributionSource.getUid()); + return permissionResult == PERMISSION_GRANTED; + } + } + private void checkRemoteViews(String pkg, String tag, int id, Notification notification) { if (removeRemoteView(pkg, tag, id, notification.contentView)) { notification.contentView = null; @@ -7789,18 +7812,17 @@ public class NotificationManagerService extends SystemService { if (notification.getSmallIcon() != null) { StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; mListeners.notifyPostedLocked(r, old); - if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) - && !isCritical(r)) { - mHandler.post(() -> { - synchronized (mNotificationLock) { - mGroupHelper.onNotificationPosted( - n, hasAutoGroupSummaryLocked(n)); - } - }); - } else if (oldSbn != null) { - final NotificationRecord finalRecord = r; - mHandler.post(() -> - mGroupHelper.onNotificationUpdated(finalRecord.getSbn())); + if (oldSbn == null + || !Objects.equals(oldSbn.getGroup(), n.getGroup()) + || oldSbn.getNotification().flags != n.getNotification().flags) { + if (!isCritical(r)) { + mHandler.post(() -> { + synchronized (mNotificationLock) { + mGroupHelper.onNotificationPosted( + n, hasAutoGroupSummaryLocked(n)); + } + }); + } } } else { Slog.e(TAG, "Not posting notification without small icon: " + notification); diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index 35b94e7ccd63..88d23ce3f9a1 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -27,6 +27,7 @@ import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeDiff; import android.util.Log; import android.util.Slog; @@ -146,13 +147,13 @@ public class ZenLog { public static void traceConfig(String reason, ZenModeConfig oldConfig, ZenModeConfig newConfig) { - ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig); - if (diff.isEmpty()) { + ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig); + if (diff == null || !diff.hasDiff()) { append(TYPE_CONFIG, reason + " no changes"); } else { append(TYPE_CONFIG, reason + ",\n" + (newConfig != null ? newConfig.toString() : null) - + ",\n" + ZenModeConfig.diff(oldConfig, newConfig)); + + ",\n" + diff); } } diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java index c29e4d78f929..52fdbda04fcd 100644 --- a/services/core/java/com/android/server/pm/IPackageManagerBase.java +++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java @@ -606,6 +606,7 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { final Computer snapshot = snapshot(); // Return null for InstantApps. if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) { + Log.w(PackageManagerService.TAG, "Returning null PackageInstaller for InstantApps"); return null; } return mInstallerService; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 5f424edb15c4..69ef3f780172 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -994,7 +994,7 @@ final class InstallPackageHelper { reconciledPackages = ReconcilePackageUtils.reconcilePackages( requests, Collections.unmodifiableMap(mPm.mPackages), versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings); + mPm.mSettings, mContext); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.setError("Reconciliation failed...", e); @@ -3588,6 +3588,11 @@ final class InstallPackageHelper { // remove the package from the system and re-scan it without any // special privileges mRemovePackageHelper.removePackage(pkg, true); + PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); + if (ps != null) { + ps.getPkgState().setUpdatedSystemApp(false); + } + try { final File codePath = new File(pkg.getPath()); synchronized (mPm.mInstallLock) { @@ -3925,7 +3930,7 @@ final class InstallPackageHelper { mPm.mPackages, Collections.singletonMap(pkgName, mPm.getSettingsVersionForPackage(parsedPackage)), mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings); + mPm.mSettings, mContext); if ((scanFlags & SCAN_AS_APEX) == 0) { appIdCreated = optimisticallyRegisterAppId(installRequest); } else { diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 5312ae6ca84c..e3c97e933ad1 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY; @@ -23,19 +24,24 @@ import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRI import static com.android.server.pm.PackageManagerService.SCAN_BOOTING; import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP; +import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; import android.os.SystemProperties; +import android.permission.PermissionManager; import android.util.ArrayMap; import android.util.Log; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedLongSparseArray; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -54,7 +60,7 @@ final class ReconcilePackageUtils { Map<String, AndroidPackage> allPackages, Map<String, Settings.VersionInfo> versionInfos, SharedLibrariesImpl sharedLibraries, - KeySetManagerService ksms, Settings settings) + KeySetManagerService ksms, Settings settings, Context context) throws ReconcileFailure { final List<ReconciledPackage> result = new ArrayList<>(installRequests.size()); @@ -143,11 +149,11 @@ final class ReconcilePackageUtils { } else { if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, - "Package " + parsedPackage.getPackageName() + "Package " + installPackageName + " upgrade keys do not match the previously installed" + " version"); } else { - String msg = "System package " + parsedPackage.getPackageName() + String msg = "System package " + installPackageName + " signature changed; retaining data."; PackageManagerService.reportSettingsProblem(Log.WARN, msg); } @@ -168,11 +174,42 @@ final class ReconcilePackageUtils { removeAppKeySetData = true; } - // if this is is a sharedUser, check to see if the new package is signed by a - // newer - // signing certificate than the existing one, and if so, copy over the new + // if this is a sharedUser, check to see if the new package is signed by a + // newer signing certificate than the existing one, and if so, copy over the new // details if (sharedUserSetting != null) { + if (!parsedPackage.isTestOnly() && sharedUserSetting.isPrivileged() + && !signatureCheckPs.isSystem()) { + final List<ParsedUsesPermission> usesPermissions = + parsedPackage.getUsesPermissions(); + final List<String> usesPrivilegedPermissions = new ArrayList<>(); + final PermissionManager permissionManager = context.getSystemService( + PermissionManager.class); + // Check if the app requests any privileged permissions because that + // violates the privapp-permissions allowlist check during boot. + if (permissionManager != null) { + for (int i = 0; i < usesPermissions.size(); i++) { + final String permissionName = usesPermissions.get(i).getName(); + final PermissionInfo permissionInfo = + permissionManager.getPermissionInfo(permissionName, 0); + if (permissionInfo != null + && (permissionInfo.getProtectionFlags() + & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) { + usesPrivilegedPermissions.add(permissionName); + } + } + } + + if (!usesPrivilegedPermissions.isEmpty()) { + throw new ReconcileFailure(INSTALL_FAILED_INVALID_APK, + "Non-system package: " + installPackageName + + " shares signature and sharedUserId with" + + " a privileged package but requests" + + " privileged permissions that are not" + + " allowed: " + Arrays.toString( + usesPrivilegedPermissions.toArray())); + } + } // Attempt to merge the existing lineage for the shared SigningDetails with // the lineage of the new package; if the shared SigningDetails are not // returned this indicates the new package added new signers to the lineage @@ -189,7 +226,7 @@ final class ReconcilePackageUtils { for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) { if (androidPackage.getPackageName() != null && !androidPackage.getPackageName().equals( - parsedPackage.getPackageName())) { + installPackageName)) { mergedDetails = mergedDetails.mergeLineageWith( androidPackage.getSigningDetails(), MERGE_RESTRICTED_CAPABILITY); @@ -219,7 +256,7 @@ final class ReconcilePackageUtils { if (sharedUserSetting != null) { if (sharedUserSetting.signaturesChanged != null && !PackageManagerServiceUtils.canJoinSharedUserId( - parsedPackage.getPackageName(), parsedPackage.getSigningDetails(), + installPackageName, parsedPackage.getSigningDetails(), sharedUserSetting, PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) { if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) { @@ -240,7 +277,7 @@ final class ReconcilePackageUtils { // whichever package happened to be scanned later. throw new IllegalStateException( "Signature mismatch on system package " - + parsedPackage.getPackageName() + + installPackageName + " for shared user " + sharedUserSetting); } @@ -252,7 +289,7 @@ final class ReconcilePackageUtils { sharedUserSetting.signaturesChanged = Boolean.TRUE; } // File a report about this. - String msg = "System package " + parsedPackage.getPackageName() + String msg = "System package " + installPackageName + " signature changed; retaining data."; PackageManagerService.reportSettingsProblem(Log.WARN, msg); } catch (IllegalArgumentException e) { diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index ad77ef7ca975..9127a93a46ee 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -187,6 +187,9 @@ public final class SuspendPackageHelper { if (changed) { changedPackagesList.add(packageName); changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + } else { + Slog.w(TAG, "No change is needed for package: " + packageName + + ". Skipping suspending/un-suspending."); } } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index e5e32f0a9690..4d2b119d7800 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -206,8 +206,6 @@ final class DefaultPermissionGrantPolicy { static { SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS); SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); - SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE); - SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND); } private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>(); 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 c5f939a2a66e..297ad73e054b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1332,9 +1332,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // Bg location is one-off runtime modifier permission and has no app op if (sPlatformPermissions.containsKey(permission) && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission) - && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission) - && !Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND - .equals(permission)) { + && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) { Slog.wtf(LOG_TAG, "Platform runtime permission " + permission + " with no app op defined!"); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index cc2c9adfcba4..3492b2660c4a 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -23,6 +23,7 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT; +import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED; import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; @@ -3655,6 +3656,26 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt for (String permission : pkg.getRequestedPermissions()) { Integer permissionState = permissionStates.get(permission); + + if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT) + && permissionState == null) { + final PackageStateInternal ps; + final long token = Binder.clearCallingIdentity(); + try { + ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); + } finally { + Binder.restoreCallingIdentity(token); + } + final String[] useFullScreenIntentPackageNames = + mContext.getResources().getStringArray( + com.android.internal.R.array.config_useFullScreenIntentPackages); + final boolean canUseFullScreenIntent = (ps != null && ps.isSystem()) + || ArrayUtils.contains(useFullScreenIntentPackageNames, + pkg.getPackageName()); + permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED + : PERMISSION_STATE_DENIED; + } + if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) { continue; } diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 401eac6e2d19..7a5664f8135e 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -316,6 +316,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat private int resolveDatasourceOp(int code, int uid, @NonNull String packageName, @Nullable String attributionTag) { code = resolveRecordAudioOp(code, uid); + code = resolveSandboxedServiceOp(code, uid); if (attributionTag == null) { return code; } @@ -439,6 +440,28 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat return code; } + private int resolveSandboxedServiceOp(int code, int uid) { + if (!Process.isIsolated(uid) // simple check which fails-fast for the common case + || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) { + return code; + } + final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = + mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); + if (hotwordDetectionServiceIdentity != null + && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { + // Upgrade the op such that no indicators is shown for camera or audio service. This + // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA. + switch (code) { + case AppOpsManager.OP_RECORD_AUDIO: + return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED; + case AppOpsManager.OP_CAMERA: + return AppOpsManager.OP_CAMERA_SANDBOXED; + } + } + return code; + } + + private int resolveUid(int code, int uid) { // The HotwordDetectionService is an isolated service, which ordinarily cannot hold // permissions. So we allow it to assume the owning package identity for certain diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 8165958bd4ef..fc6b4e9dcb20 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -222,6 +222,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.vr.VrManagerInternal; +import com.android.server.wallpaper.WallpaperManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.DisplayPolicy; import com.android.server.wm.DisplayRotation; @@ -412,6 +413,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { SensorPrivacyManager mSensorPrivacyManager; DisplayManager mDisplayManager; DisplayManagerInternal mDisplayManagerInternal; + + private WallpaperManagerInternal mWallpaperManagerInternal; + boolean mPreloadedRecentApps; final Object mServiceAcquireLock = new Object(); Vibrator mVibrator; // Vibrator for giving feedback of orientation changes @@ -5016,11 +5020,34 @@ public class PhoneWindowManager implements WindowManagerPolicy { return bootCompleted ? mKeyguardDrawnTimeout : 5000; } + @Nullable + private WallpaperManagerInternal getWallpaperManagerInternal() { + if (mWallpaperManagerInternal == null) { + mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class); + } + return mWallpaperManagerInternal; + } + + private void reportScreenTurningOnToWallpaper(int displayId) { + WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); + if (wallpaperManagerInternal != null) { + wallpaperManagerInternal.onScreenTurningOn(displayId); + } + } + + private void reportScreenTurnedOnToWallpaper(int displayId) { + WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); + if (wallpaperManagerInternal != null) { + wallpaperManagerInternal.onScreenTurnedOn(displayId); + } + } + // Called on the DisplayManager's DisplayPowerController thread. @Override public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) { if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turning on..."); + reportScreenTurningOnToWallpaper(displayId); if (displayId == DEFAULT_DISPLAY) { Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */); @@ -5061,6 +5088,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void screenTurnedOn(int displayId) { if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turned on..."); + reportScreenTurnedOnToWallpaper(displayId); + if (displayId != DEFAULT_DISPLAY) { return; } diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 7beb1edfe51f..e437be8e01b5 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -105,36 +105,46 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { @Override public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount) { - // For native crashes, we will roll back any available rollbacks + boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class) + .getAvailableRollbacks().isEmpty(); + int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH - && !mContext.getSystemService(RollbackManager.class) - .getAvailableRollbacks().isEmpty()) { - return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; - } - if (getAvailableRollback(failedPackage) == null) { - // Don't handle the notification, no rollbacks available for the package - return PackageHealthObserverImpact.USER_IMPACT_NONE; - } else { + && anyRollbackAvailable) { + // For native crashes, we will directly roll back any available rollbacks + // Note: For non-native crashes the rollback-all step has higher impact + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + } else if (mitigationCount == 1 && getAvailableRollback(failedPackage) != null) { // Rollback is available, we may get a callback into #execute - return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + } else if (mitigationCount > 1 && anyRollbackAvailable) { + // If any rollbacks are available, we will commit them + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; } + + return impact; } @Override public boolean execute(@Nullable VersionedPackage failedPackage, @FailureReasons int rollbackReason, int mitigationCount) { if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { - mHandler.post(() -> rollbackAll()); + mHandler.post(() -> rollbackAll(rollbackReason)); return true; } - RollbackInfo rollback = getAvailableRollback(failedPackage); - if (rollback == null) { - Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage); - return false; + if (mitigationCount == 1) { + RollbackInfo rollback = getAvailableRollback(failedPackage); + if (rollback == null) { + Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage); + return false; + } + mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); + } else if (mitigationCount > 1) { + mHandler.post(() -> rollbackAll(rollbackReason)); } - mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); - // Assume rollback executed successfully + + // Assume rollbacks executed successfully return true; } @@ -468,7 +478,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { } @WorkerThread - private void rollbackAll() { + private void rollbackAll(@FailureReasons int rollbackReason) { assertInWorkerThread(); RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks(); @@ -487,7 +497,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { for (RollbackInfo rollback : rollbacks) { VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom(); - rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); + rollbackPackage(rollback, sample, rollbackReason); } } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java index 584fbddee478..3699557706fd 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java @@ -27,4 +27,10 @@ public abstract class WallpaperManagerInternal { * Notifies the display is ready for adding wallpaper on it. */ public abstract void onDisplayReady(int displayId); + + /** Notifies when the screen finished turning on and is visible to the user. */ + public abstract void onScreenTurnedOn(int displayId); + + /** Notifies when the screen starts turning on and is not yet visible to the user. */ + public abstract void onScreenTurningOn(int displayId); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index b1b0c559aad4..e17866922990 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1613,6 +1613,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onDisplayReady(int displayId) { onDisplayReadyInternal(displayId); } + + @Override + public void onScreenTurnedOn(int displayId) { + notifyScreenTurnedOn(displayId); + } + @Override + public void onScreenTurningOn(int displayId) { + notifyScreenTurningOn(displayId); + } } void initialize() { @@ -2442,6 +2451,54 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + /** + * Propagates screen turned on event to wallpaper engine. + */ + @Override + public void notifyScreenTurnedOn(int displayId) { + synchronized (mLock) { + final WallpaperData data = mWallpaperMap.get(mCurrentUserId); + if (data != null + && data.connection != null + && data.connection.containsDisplay(displayId)) { + final IWallpaperEngine engine = data.connection + .getDisplayConnectorOrCreate(displayId).mEngine; + if (engine != null) { + try { + engine.onScreenTurnedOn(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + } + } + + + + /** + * Propagate screen turning on event to wallpaper engine. + */ + @Override + public void notifyScreenTurningOn(int displayId) { + synchronized (mLock) { + final WallpaperData data = mWallpaperMap.get(mCurrentUserId); + if (data != null + && data.connection != null + && data.connection.containsDisplay(displayId)) { + final IWallpaperEngine engine = data.connection + .getDisplayConnectorOrCreate(displayId).mEngine; + if (engine != null) { + try { + engine.onScreenTurningOn(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + } + } + @Override public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) { checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW); @@ -3569,6 +3626,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void dumpWallpaper(WallpaperData wallpaper, PrintWriter pw) { if (wallpaper == null) { pw.println(" (null entry)"); + return; } pw.print(" User "); pw.print(wallpaper.userId); pw.print(": id="); pw.print(wallpaper.wallpaperId); diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index 83804f75afea..32f7b9682fb7 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -466,8 +466,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, } boolean isAnimatingByRecents(@NonNull Task task) { - return task.isAnimatingByRecents() - || mService.mAtmService.getTransitionController().inRecentsTransition(task); + return task.isAnimatingByRecents(); } void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index fa3a186a6153..f3001133338a 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -25,6 +25,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFI import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; +import static com.android.internal.util.DumpUtils.dumpSparseArray; +import static com.android.internal.util.DumpUtils.dumpSparseArrayValues; import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY; import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER; import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H; @@ -542,15 +544,12 @@ final class AccessibilityController { } void dump(PrintWriter pw, String prefix) { - for (int i = 0; i < mDisplayMagnifiers.size(); i++) { - final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.valueAt(i); - if (displayMagnifier != null) { - displayMagnifier.dump(pw, prefix - + "Magnification display# " + mDisplayMagnifiers.keyAt(i)); - } - } - pw.println(prefix - + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver); + dumpSparseArray(pw, prefix, mDisplayMagnifiers, "magnification display", + (index, key) -> pw.printf("%sDisplay #%d:", prefix + " ", key), + dm -> dm.dump(pw, "")); + dumpSparseArrayValues(pw, prefix, mWindowsForAccessibilityObserver, + "windows for accessibility observer"); + mAccessibilityWindowsPopulator.dump(pw, prefix); } void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) { diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index 21b241a0d117..70f20075b48c 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static com.android.internal.util.DumpUtils.KeyDumper; +import static com.android.internal.util.DumpUtils.ValueDumper; +import static com.android.internal.util.DumpUtils.dumpSparseArray; import static com.android.server.wm.utils.RegionUtils.forEachRect; import android.annotation.NonNull; @@ -40,6 +43,7 @@ import android.window.WindowInfosListener; import com.android.internal.annotations.GuardedBy; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -562,6 +566,35 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { notifyWindowsChanged(displayIdsForWindowsChanged); } + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.println("AccessibilityWindowsPopulator"); + String prefix2 = prefix + " "; + + pw.print(prefix2); pw.print("mWindowsNotificationEnabled: "); + pw.println(mWindowsNotificationEnabled); + + if (mVisibleWindows.isEmpty()) { + pw.print(prefix2); pw.println("No visible windows"); + } else { + pw.print(prefix2); pw.print(mVisibleWindows.size()); + pw.print(" visible windows: "); pw.println(mVisibleWindows); + } + KeyDumper noKeyDumper = (i, k) -> {}; // display id is already shown on value; + KeyDumper displayDumper = (i, d) -> pw.printf("%sDisplay #%d: ", prefix, d); + // Ideally magnificationSpecDumper should use spec.dump(pw), but there is no such method + ValueDumper<MagnificationSpec> magnificationSpecDumper = spec -> pw.print(spec); + + dumpSparseArray(pw, prefix2, mDisplayInfos, "display info", noKeyDumper, d -> pw.print(d)); + dumpSparseArray(pw, prefix2, mInputWindowHandlesOnDisplays, "window handles on display", + displayDumper, list -> pw.print(list)); + dumpSparseArray(pw, prefix2, mMagnificationSpecInverseMatrix, "magnification spec matrix", + noKeyDumper, matrix -> matrix.dump(pw)); + dumpSparseArray(pw, prefix2, mCurrentMagnificationSpec, "current magnification spec", + noKeyDumper, magnificationSpecDumper); + dumpSparseArray(pw, prefix2, mPreviousMagnificationSpec, "previous magnification spec", + noKeyDumper, magnificationSpecDumper); + } + @GuardedBy("mLock") private void releaseResources() { mInputWindowHandlesOnDisplays.clear(); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ff1c28ad1973..7718dd88b772 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -78,8 +78,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManagerInternal; -import android.content.pm.ParceledListSlice; -import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.os.Binder; import android.os.Bundle; @@ -1147,8 +1145,7 @@ class ActivityClientController extends IActivityClientController.Stub { if (mService.mWindowManager.mSyncEngine.hasActiveSync()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Multiwindow Fullscreen Request: %s", transition); - mService.mWindowManager.mSyncEngine.queueSyncSet( - () -> r.mTransitionController.moveToCollecting(transition), + r.mTransitionController.queueCollecting(transition, () -> { executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition, true /* queued */); @@ -1645,18 +1642,15 @@ class ActivityClientController extends IActivityClientController.Stub { launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME); } - // If the activity is one of the main entry points for the application, then we should + // If the activity was launched directly from the home screen, then we should // refrain from finishing the activity and instead move it to the back to keep it in // memory. The requirements for this are: // 1. The activity is the last running activity in the task. // 2. The current activity is the base activity for the task. - // 3. a. If the activity was launched by the home process, we trust that its intent - // was resolved, so we check if the it is a main intent for the application. - // b. Otherwise, we query Package Manager to verify whether the activity is a - // launcher activity for the application. + // 3. The activity was launched by the home process, and is one of the main entry + // points for the application. if (baseActivityIntent != null && isLastRunningActivity - && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) - || isLauncherActivity(baseActivityIntent.getComponent()))) { + && launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) { moveActivityTaskToBack(token, true /* nonRoot */); return; } @@ -1668,31 +1662,6 @@ class ActivityClientController extends IActivityClientController.Stub { } } - /** - * Queries PackageManager to see if the given activity is one of the main entry point for the - * application. This should not be called with the WM lock held. - */ - @SuppressWarnings("unchecked") - private boolean isLauncherActivity(@NonNull ComponentName activity) { - final Intent queryIntent = new Intent(Intent.ACTION_MAIN); - queryIntent.addCategory(Intent.CATEGORY_LAUNCHER); - queryIntent.setPackage(activity.getPackageName()); - try { - final ParceledListSlice<ResolveInfo> resolved = - mService.getPackageManager().queryIntentActivities( - queryIntent, null, 0, mContext.getUserId()); - if (resolved == null) return false; - for (final ResolveInfo ri : resolved.getList()) { - if (ri.getComponentInfo().getComponentName().equals(activity)) { - return true; - } - } - } catch (RemoteException e) { - Slog.e(TAG, "Failed to query intent activities", e); - } - return false; - } - @Override public void enableTaskLocaleOverride(IBinder token) { if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b5fde9e7593b..0b98495c8e99 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3522,7 +3522,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean endTask = task.getTopNonFinishingActivity() == null && !task.isClearingToReuseTask(); - mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this); + final Transition newTransition = + mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this); if (isState(RESUMED)) { if (endTask) { mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted( @@ -3543,8 +3544,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // the best capture timing (e.g. IME window capture), // No need additional task capture while task is controlled by RecentsAnimation. if (mAtmService.mWindowManager.mTaskSnapshotController != null - && !(task.isAnimatingByRecents() - || mTransitionController.inRecentsTransition(task))) { + && !task.isAnimatingByRecents()) { final ArraySet<Task> tasks = Sets.newArraySet(task); mAtmService.mWindowManager.mTaskSnapshotController.snapshotTasks(tasks); mAtmService.mWindowManager.mTaskSnapshotController @@ -3576,7 +3576,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (!isState(PAUSING)) { if (mVisibleRequested) { // Prepare and execute close transition. - prepareActivityHideTransitionAnimation(); + if (mTransitionController.isShellTransitionsEnabled()) { + setVisibility(false); + if (newTransition != null) { + // This is a transition specifically for this close operation, so set + // ready now. + newTransition.setReady(mDisplayContent, true); + } + } else { + prepareActivityHideTransitionAnimation(); + } } final boolean removedActivity = completeFinishing("finishIfPossible") == null; @@ -9762,6 +9771,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * directly with keeping its record. */ void restartProcessIfVisible() { + if (finishing) return; Slog.i(TAG, "Request to restart process of " + this); // Reset the existing override configuration so it can be updated according to the latest @@ -9810,8 +9820,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A scheduleStopForRestartProcess(); }; if (mWmService.mSyncEngine.hasActiveSync()) { - mWmService.mSyncEngine.queueSyncSet( - () -> mTransitionController.moveToCollecting(transition), executeRestart); + mTransitionController.queueCollecting(transition, executeRestart); } else { mTransitionController.moveToCollecting(transition); executeRestart.run(); @@ -10524,11 +10533,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean isSyncFinished() { - if (task != null && mTransitionController.isTransientHide(task)) { - // The activity keeps visibleRequested but may be hidden later, so no need to wait for - // it to be drawn. - return true; - } if (!super.isSyncFinished()) return false; if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController .isVisibilityUnknown(this)) { diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 7c1e9071b926..bfe298653584 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -577,7 +577,6 @@ public class ActivityStartController { final Transition transition = controller.getCollectingTransition(); if (transition != null) { transition.setRemoteAnimationApp(r.app.getThread()); - controller.collect(task); controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask)); } task.moveToFront("startExistingRecents"); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 38f13ec15987..c5e75faf2c6c 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2548,6 +2548,7 @@ class ActivityStarter { mAvoidMoveToFront = false; mFrozeTaskList = false; mTransientLaunch = false; + mPriorAboveTask = null; mDisplayLockAndOccluded = false; mVoiceSession = null; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 12fe6a0dba25..e780716dd06c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -831,7 +831,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private final Runnable mUpdateOomAdjRunnable = new Runnable() { @Override public void run() { - mAmInternal.updateOomAdj(); + mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY); } }; @@ -2874,8 +2874,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, getTransitionController(), mWindowManager.mSyncEngine); if (mWindowManager.mSyncEngine.hasActiveSync()) { - mWindowManager.mSyncEngine.queueSyncSet( - () -> getTransitionController().moveToCollecting(transition), + getTransitionController().queueCollecting(transition, () -> { if (!task.getWindowConfiguration().canResizeTask()) { Slog.w(TAG, "resizeTask not allowed on task=" + task); @@ -3629,9 +3628,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Pip-Enter: %s", transition); - mWindowManager.mSyncEngine.queueSyncSet( - () -> getTransitionController().moveToCollecting(transition), - enterPipRunnable); + getTransitionController().queueCollecting(transition, enterPipRunnable); } else { // Move to collecting immediately to "claim" the sync-engine for this // transition. @@ -3647,9 +3644,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Pip-Enter: %s", transition); - mWindowManager.mSyncEngine.queueSyncSet( - () -> getTransitionController().moveToCollecting(transition), - enterPipRunnable); + getTransitionController().queueCollecting(transition, enterPipRunnable); } else { if (transition != null) { getTransitionController().moveToCollecting(transition); diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 85974c7ecf17..d916a1be1a03 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -23,6 +23,7 @@ import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Handler; import android.os.Trace; import android.util.ArraySet; import android.util.Slog; @@ -172,7 +173,7 @@ class BLASTSyncEngine { if (ran) { return; } - mWm.mH.removeCallbacks(this); + mHandler.removeCallbacks(this); ran = true; SurfaceControl.Transaction t = new SurfaceControl.Transaction(); for (WindowContainer wc : wcAwaitingCommit) { @@ -199,13 +200,13 @@ class BLASTSyncEngine { }; CommitCallback callback = new CommitCallback(); merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted); - mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION); + mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady"); mListener.onTransactionReady(mSyncId, merged); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); mActiveSyncs.remove(mSyncId); - mWm.mH.removeCallbacks(mOnTimeout); + mHandler.removeCallbacks(mOnTimeout); // Immediately start the next pending sync-transaction if there is one. if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) { @@ -216,7 +217,7 @@ class BLASTSyncEngine { throw new IllegalStateException("Pending Sync Set didn't start a sync."); } // Post this so that the now-playing transition setup isn't interrupted. - mWm.mH.post(() -> { + mHandler.post(() -> { synchronized (mWm.mGlobalLock) { pt.mApplySync.run(); } @@ -269,6 +270,7 @@ class BLASTSyncEngine { } private final WindowManagerService mWm; + private final Handler mHandler; private int mNextSyncId = 0; private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>(); @@ -280,7 +282,13 @@ class BLASTSyncEngine { private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>(); BLASTSyncEngine(WindowManagerService wms) { + this(wms, wms.mH); + } + + @VisibleForTesting + BLASTSyncEngine(WindowManagerService wms, Handler mainHandler) { mWm = wms; + mHandler = mainHandler; } /** @@ -305,8 +313,8 @@ class BLASTSyncEngine { if (mActiveSyncs.size() != 0) { // We currently only support one sync at a time, so start a new SyncGroup when there is // another may cause issue. - ProtoLog.w(WM_DEBUG_SYNC_ENGINE, - "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId); + Slog.e(TAG, "SyncGroup " + s.mSyncId + + ": Started when there is other active SyncGroup"); } mActiveSyncs.put(s.mSyncId, s); ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", @@ -325,7 +333,7 @@ class BLASTSyncEngine { @VisibleForTesting void scheduleTimeout(SyncGroup s, long timeoutMs) { - mWm.mH.postDelayed(s.mOnTimeout, timeoutMs); + mHandler.postDelayed(s.mOnTimeout, timeoutMs); } void addToSyncSet(int id, WindowContainer wc) { diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index be80b010962b..745374301263 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -286,8 +286,8 @@ class BackNavigationController { currentActivity.getCustomAnimation(false/* open */); if (customAppTransition != null) { infoBuilder.setCustomAnimation(currentActivity.packageName, - customAppTransition.mExitAnim, customAppTransition.mEnterAnim, + customAppTransition.mExitAnim, customAppTransition.mBackgroundColor); } } @@ -648,31 +648,28 @@ class BackNavigationController { if (finishedTransition == mWaitTransitionFinish) { clearBackAnimations(); } + if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) { return false; } - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Handling the deferred animation after transition finished"); - // Show the target surface and its parents to prevent it or its parents hidden when - // the transition finished. - // The target could be affected by transition when : - // Open transition -> the open target in back navigation - // Close transition -> the close target in back navigation. + // Find the participated container collected by transition when : + // Open transition -> the open target in back navigation, the close target in transition. + // Close transition -> the close target in back navigation, the open target in transition. boolean hasTarget = false; - final SurfaceControl.Transaction t = - mPendingAnimationBuilder.mCloseTarget.getPendingTransaction(); - for (int i = 0; i < targets.size(); i++) { - final WindowContainer wc = targets.get(i).mContainer; - if (wc.asActivityRecord() == null && wc.asTask() == null) { - continue; - } else if (!mPendingAnimationBuilder.containTarget(wc)) { + for (int i = 0; i < finishedTransition.mParticipants.size(); i++) { + final WindowContainer wc = finishedTransition.mParticipants.valueAt(i); + if (wc.asActivityRecord() == null && wc.asTask() == null + && wc.asTaskFragment() == null) { continue; } - hasTarget = true; - t.show(wc.getSurfaceControl()); + if (mPendingAnimationBuilder.containTarget(wc)) { + hasTarget = true; + break; + } } if (!hasTarget) { @@ -689,6 +686,12 @@ class BackNavigationController { return false; } + // Ensure the final animation targets which hidden by transition could be visible. + for (int i = 0; i < targets.size(); i++) { + final WindowContainer wc = targets.get(i).mContainer; + wc.prepareSurfaces(); + } + scheduleAnimation(mPendingAnimationBuilder); mPendingAnimationBuilder = null; return true; @@ -1076,7 +1079,7 @@ class BackNavigationController { boolean containTarget(@NonNull WindowContainer wc) { return wc == mOpenTarget || wc == mCloseTarget - || wc.hasChild(mOpenTarget) || wc.hasChild(mCloseTarget); + || mOpenTarget.hasChild(wc) || mCloseTarget.hasChild(wc); } /** @@ -1151,6 +1154,11 @@ class BackNavigationController { private static void setLaunchBehind(@NonNull ActivityRecord activity) { if (!activity.isVisibleRequested()) { activity.setVisibility(true); + // The transition could commit the visibility and in the finishing state, that could + // skip commitVisibility call in setVisibility cause the activity won't visible here. + // Call it again to make sure the activity could be visible while handling the pending + // animation. + activity.commitVisibility(true, true); } activity.mLaunchTaskBehind = true; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index bec58b848478..c2bc4591ce0d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -775,6 +775,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ DisplayWindowPolicyControllerHelper mDwpcHelper; + private final DisplayRotationReversionController mRotationReversionController; + private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; final ActivityRecord activity = w.mActivityRecord; @@ -1204,6 +1206,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ false) ? new DisplayRotationCompatPolicy(this) : null; + mRotationReversionController = new DisplayRotationReversionController(this); mInputMonitor = new InputMonitor(mWmService, this); mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this); @@ -1333,6 +1336,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .show(mA11yOverlayLayer); } + DisplayRotationReversionController getRotationReversionController() { + return mRotationReversionController; + } + boolean isReady() { // The display is ready when the system and the individual display are both ready. return mWmService.mDisplayReady && mDisplayReady; @@ -1711,9 +1718,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private boolean updateOrientation(boolean forceUpdate) { + final WindowContainer prevOrientationSource = mLastOrientationSource; final int orientation = getOrientation(); // The last orientation source is valid only after getOrientation. final WindowContainer orientationSource = getLastOrientationSource(); + if (orientationSource != prevOrientationSource + && mRotationReversionController.isRotationReversionEnabled()) { + mRotationReversionController.updateForNoSensorOverride(); + } final ActivityRecord r = orientationSource != null ? orientationSource.asActivityRecord() : null; if (r != null) { diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index c8fde6b5b355..20048ce543f3 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -33,6 +33,9 @@ import static com.android.server.wm.DisplayRotationProto.IS_FIXED_TO_USER_ROTATI import static com.android.server.wm.DisplayRotationProto.LAST_ORIENTATION; import static com.android.server.wm.DisplayRotationProto.ROTATION; import static com.android.server.wm.DisplayRotationProto.USER_ROTATION; +import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT; +import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_HALF_FOLD; +import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE; @@ -106,6 +109,9 @@ public class DisplayRotation { int mExit; } + @Nullable + final FoldController mFoldController; + private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final DisplayPolicy mDisplayPolicy; @@ -127,8 +133,6 @@ public class DisplayRotation { private OrientationListener mOrientationListener; private StatusBarManagerInternal mStatusBarManagerInternal; private SettingsObserver mSettingsObserver; - @Nullable - private FoldController mFoldController; @NonNull private final DeviceStateController mDeviceStateController; @NonNull @@ -299,7 +303,11 @@ public class DisplayRotation { if (mSupportAutoRotation && mContext.getResources().getBoolean( R.bool.config_windowManagerHalfFoldAutoRotateOverride)) { mFoldController = new FoldController(); + } else { + mFoldController = null; } + } else { + mFoldController = null; } } @@ -357,6 +365,11 @@ public class DisplayRotation { return -1; } + @VisibleForTesting + boolean useDefaultSettingsProvider() { + return isDefaultDisplay; + } + /** * Updates the configuration which may have different values depending on current user, e.g. * runtime resource overlay. @@ -903,7 +916,7 @@ public class DisplayRotation { @VisibleForTesting void setUserRotation(int userRotationMode, int userRotation) { mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED; - if (isDefaultDisplay) { + if (useDefaultSettingsProvider()) { // We'll be notified via settings listener, so we don't need to update internal values. final ContentResolver res = mContext.getContentResolver(); final int accelerometerRotation = @@ -1859,7 +1872,7 @@ public class DisplayRotation { return false; } if (mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) { - return !(isTabletop ^ mTabletopRotations.contains(mRotation)); + return isTabletop == mTabletopRotations.contains(mRotation); } return true; } @@ -1883,14 +1896,17 @@ public class DisplayRotation { return mDeviceState == DeviceStateController.DeviceState.OPEN && !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving && mInHalfFoldTransition - && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted. + && mDisplayContent.getRotationReversionController().isOverrideActive( + REVERSION_TYPE_HALF_FOLD) && mUserRotationMode - == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked. + == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked. } int revertOverriddenRotation() { int savedRotation = mHalfFoldSavedRotation; mHalfFoldSavedRotation = -1; + mDisplayContent.getRotationReversionController() + .revertOverride(REVERSION_TYPE_HALF_FOLD); mInHalfFoldTransition = false; return savedRotation; } @@ -1910,6 +1926,8 @@ public class DisplayRotation { && mDeviceState != DeviceStateController.DeviceState.HALF_FOLDED) { // The device has transitioned to HALF_FOLDED state: save the current rotation and // update the device rotation. + mDisplayContent.getRotationReversionController().beforeOverrideApplied( + REVERSION_TYPE_HALF_FOLD); mHalfFoldSavedRotation = mRotation; mDeviceState = newState; // Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will @@ -2115,6 +2133,8 @@ public class DisplayRotation { final int mHalfFoldSavedRotation; final boolean mInHalfFoldTransition; final DeviceStateController.DeviceState mDeviceState; + @Nullable final boolean[] mRotationReversionSlots; + @Nullable final String mDisplayRotationCompatPolicySummary; Record(DisplayRotation dr, int fromRotation, int toRotation) { @@ -2155,6 +2175,8 @@ public class DisplayRotation { ? null : dc.mDisplayRotationCompatPolicy .getSummaryForDisplayRotationHistoryRecord(); + mRotationReversionSlots = + dr.mDisplayContent.getRotationReversionController().getSlotsCopy(); } void dump(String prefix, PrintWriter pw) { @@ -2180,6 +2202,12 @@ public class DisplayRotation { if (mDisplayRotationCompatPolicySummary != null) { pw.println(prefix + mDisplayRotationCompatPolicySummary); } + if (mRotationReversionSlots != null) { + pw.println(prefix + " reversionSlots= NOSENSOR " + + mRotationReversionSlots[REVERSION_TYPE_NOSENSOR] + ", CAMERA " + + mRotationReversionSlots[REVERSION_TYPE_CAMERA_COMPAT] + " HALF_FOLD " + + mRotationReversionSlots[REVERSION_TYPE_HALF_FOLD]); + } } } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index fb72d6c6b56d..ae93a9496f7c 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -33,6 +33,7 @@ import static android.view.Display.TYPE_INTERNAL; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; +import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT; import android.annotation.NonNull; import android.annotation.Nullable; @@ -156,6 +157,11 @@ final class DisplayRotationCompatPolicy { @ScreenOrientation int getOrientation() { mLastReportedOrientation = getOrientationInternal(); + if (mLastReportedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { + rememberOverriddenOrientationIfNeeded(); + } else { + restoreOverriddenOrientationIfNeeded(); + } return mLastReportedOrientation; } @@ -277,6 +283,34 @@ final class DisplayRotationCompatPolicy { + " }"; } + private void restoreOverriddenOrientationIfNeeded() { + if (!isOrientationOverridden()) { + return; + } + if (mDisplayContent.getRotationReversionController().revertOverride( + REVERSION_TYPE_CAMERA_COMPAT)) { + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Reverting orientation after camera compat force rotation"); + // Reset last orientation source since we have reverted the orientation. + mDisplayContent.mLastOrientationSource = null; + } + } + + private boolean isOrientationOverridden() { + return mDisplayContent.getRotationReversionController().isOverrideActive( + REVERSION_TYPE_CAMERA_COMPAT); + } + + private void rememberOverriddenOrientationIfNeeded() { + if (!isOrientationOverridden()) { + mDisplayContent.getRotationReversionController().beforeOverrideApplied( + REVERSION_TYPE_CAMERA_COMPAT); + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Saving original orientation before camera compat, last orientation is %d", + mDisplayContent.getLastOrientation()); + } + } + // Refreshing only when configuration changes after rotation. private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig, Configuration lastReportedConfig) { diff --git a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java new file mode 100644 index 000000000000..d3a8a82f8f87 --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java @@ -0,0 +1,148 @@ +/* + * 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.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; + +import android.annotation.Nullable; +import android.app.WindowConfiguration; +import android.content.ActivityInfoProto; +import android.view.Surface; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.policy.WindowManagerPolicy; + +/** + * Defines the behavior of reversion from device rotation overrides. + * + * <p>There are 3 override types: + * <ol> + * <li>The top application has {@link SCREEN_ORIENTATION_NOSENSOR} set and is rotated to + * {@link ROTATION_0}. + * <li>Camera compat treatment has rotated the app {@link DisplayRotationCompatPolicy}. + * <li>The device is half-folded and has auto-rotate is temporarily enabled. + * </ol> + * + * <p>Before an override is enabled, a component should call {@code beforeOverrideApplied}. When + * it wishes to revert, it should call {@code revertOverride}. The user rotation will be restored + * if there are no other overrides present. + */ +final class DisplayRotationReversionController { + + static final int REVERSION_TYPE_NOSENSOR = 0; + static final int REVERSION_TYPE_CAMERA_COMPAT = 1; + static final int REVERSION_TYPE_HALF_FOLD = 2; + private static final int NUM_SLOTS = 3; + + @Surface.Rotation + private int mUserRotationOverridden = WindowConfiguration.ROTATION_UNDEFINED; + @WindowManagerPolicy.UserRotationMode + private int mUserRotationModeOverridden; + + private final boolean[] mSlots = new boolean[NUM_SLOTS]; + private final DisplayContent mDisplayContent; + + DisplayRotationReversionController(DisplayContent content) { + mDisplayContent = content; + } + + boolean isRotationReversionEnabled() { + return mDisplayContent.mDisplayRotationCompatPolicy != null + || mDisplayContent.getDisplayRotation().mFoldController != null + || mDisplayContent.getIgnoreOrientationRequest(); + } + + void beforeOverrideApplied(int slotIndex) { + if (mSlots[slotIndex]) return; + maybeSaveUserRotation(); + mSlots[slotIndex] = true; + } + + boolean isOverrideActive(int slotIndex) { + return mSlots[slotIndex]; + } + + @Nullable + boolean[] getSlotsCopy() { + return isRotationReversionEnabled() ? mSlots.clone() : null; + } + + void updateForNoSensorOverride() { + if (!mSlots[REVERSION_TYPE_NOSENSOR]) { + if (isTopFullscreenActivityNoSensor()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override detected"); + beforeOverrideApplied(REVERSION_TYPE_NOSENSOR); + } + } else { + if (!isTopFullscreenActivityNoSensor()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override is absent: reverting"); + revertOverride(REVERSION_TYPE_NOSENSOR); + } + } + } + + boolean isAnyOverrideActive() { + for (int i = 0; i < NUM_SLOTS; ++i) { + if (mSlots[i]) { + return true; + } + } + return false; + } + + boolean revertOverride(int slotIndex) { + if (!mSlots[slotIndex]) return false; + mSlots[slotIndex] = false; + if (isAnyOverrideActive()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Other orientation overrides are in place: not reverting"); + return false; + } + // Only override if the rotation is frozen and there are no other active slots. + if (mDisplayContent.getDisplayRotation().isRotationFrozen()) { + mDisplayContent.getDisplayRotation().setUserRotation( + mUserRotationModeOverridden, + mUserRotationOverridden); + return true; + } else { + return false; + } + } + + private void maybeSaveUserRotation() { + if (!isAnyOverrideActive()) { + mUserRotationModeOverridden = + mDisplayContent.getDisplayRotation().getUserRotationMode(); + mUserRotationOverridden = mDisplayContent.getDisplayRotation().getUserRotation(); + } + } + + private boolean isTopFullscreenActivityNoSensor() { + final Task topFullscreenTask = + mDisplayContent.getTask( + t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + if (topFullscreenTask != null) { + final ActivityRecord topActivity = + topFullscreenTask.topRunningActivity(); + return topActivity != null && topActivity.getOrientation() + == ActivityInfoProto.SCREEN_ORIENTATION_NOSENSOR; + } + return false; + } +} diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index f8f0211e108f..f5079d37b324 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1512,7 +1512,7 @@ class RecentTasks { // callbacks here. final Task removedTask = mTasks.remove(removeIndex); if (removedTask != task) { - if (removedTask.hasChild()) { + if (removedTask.hasChild() && !removedTask.isActivityTypeHome()) { Slog.i(TAG, "Add " + removedTask + " to hidden list because adding " + task); // A non-empty task is replaced by a new task. Because the removed task is no longer // managed by the recent tasks list, add it to the hidden list to prevent the task diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 57fca3aeea79..237846997e9e 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -146,8 +146,6 @@ public class RecentsAnimationController implements DeathRecipient { @VisibleForTesting boolean mIsAddingTaskToTargets; - @VisibleForTesting - boolean mShouldAttachNavBarToAppDuringTransition; private boolean mNavigationBarAttachedToApp; private ActivityRecord mNavBarAttachedApp; @@ -379,8 +377,6 @@ public class RecentsAnimationController implements DeathRecipient { mDisplayId = displayId; mStatusBar = LocalServices.getService(StatusBarManagerInternal.class); mDisplayContent = service.mRoot.getDisplayContent(displayId); - mShouldAttachNavBarToAppDuringTransition = - mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition(); } /** @@ -577,7 +573,7 @@ public class RecentsAnimationController implements DeathRecipient { } private void attachNavigationBarToApp() { - if (!mShouldAttachNavBarToAppDuringTransition + if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() // Skip the case where the nav bar is controlled by fade rotation. || mDisplayContent.getAsyncRotationController() != null) { return; @@ -652,7 +648,7 @@ public class RecentsAnimationController implements DeathRecipient { } void animateNavigationBarForAppLaunch(long duration) { - if (!mShouldAttachNavBarToAppDuringTransition + if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() // Skip the case where the nav bar is controlled by fade rotation. || mDisplayContent.getAsyncRotationController() != null || mNavigationBarAttachedToApp diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 07daa4b22ac9..ad934541267e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2335,9 +2335,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> transition.playNow(); }; if (display.mTransitionController.isCollecting()) { - mWmService.mSyncEngine.queueSyncSet( - () -> display.mTransitionController.moveToCollecting(transition), - sendSleepTransition); + display.mTransitionController.queueCollecting(transition, sendSleepTransition); } else { display.mTransitionController.moveToCollecting(transition); sendSleepTransition.run(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 0857898ca1d2..89f975387667 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3006,7 +3006,8 @@ class Task extends TaskFragment { /** Checking if self or its child tasks are animated by recents animation. */ boolean isAnimatingByRecents() { - return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS); + return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS) + || mTransitionController.isTransientHide(this); } WindowState getTopVisibleAppMainWindow() { @@ -4687,7 +4688,7 @@ class Task extends TaskFragment { if (!isAttached()) { return; } - mTransitionController.collect(this); + mTransitionController.recordTaskOrder(this); final TaskDisplayArea taskDisplayArea = getDisplayArea(); @@ -5634,8 +5635,7 @@ class Task extends TaskFragment { if (mWmService.mSyncEngine.hasActiveSync()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Move-to-back: %s", transition); - mWmService.mSyncEngine.queueSyncSet( - () -> mTransitionController.moveToCollecting(transition), + mTransitionController.queueCollecting(transition, () -> { // Need to check again since this happens later and the system might // be in a different state. @@ -5683,6 +5683,7 @@ class Task extends TaskFragment { // Usually resuming a top activity triggers the next app transition, but nothing's got // resumed in this case, so we need to execute it explicitly. mDisplayContent.executeAppTransition(); + mDisplayContent.setFocusedApp(topActivity); } else { mRootWindowContainer.resumeFocusedTasksTopActivities(); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 3cc154892e33..652c2977f40e 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -109,7 +109,7 @@ import java.util.function.Predicate; */ class Transition implements BLASTSyncEngine.TransactionReadyListener { private static final String TAG = "Transition"; - private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition"; + private static final String TRACE_NAME_PLAY_TRANSITION = "playing"; /** The default package for resources */ private static final String DEFAULT_PACKAGE = "android"; @@ -287,7 +287,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (restoreBelow != null) { final Task transientRootTask = activity.getRootTask(); - // Collect all visible activities which can be occluded by the transient activity to + // Collect all visible tasks which can be occluded by the transient activity to // make sure they are in the participants so their visibilities can be updated when // finishing transition. ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> { @@ -297,11 +297,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mTransientHideTasks.add(t); } if (t.isLeafTask()) { - t.forAllActivities(r -> { - if (r.isVisibleRequested()) { - collect(r); - } - }); + collect(t); } } return t == restoreBelow; @@ -511,8 +507,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mParticipants.contains(wc)) return; // Wallpaper is like in a static drawn state unless display may have changes, so exclude // the case to reduce transition latency waiting for the unchanged wallpaper to redraw. - final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent); - if (needSyncDraw) { + final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent)) + // Transient-hide may be hidden later, so no need to request redraw. + && !isInTransientHide(wc); + if (needSync) { mSyncEngine.addToSyncSet(mSyncId, wc); } ChangeInfo info = mChanges.get(wc); @@ -521,10 +519,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mChanges.put(wc, info); } mParticipants.add(wc); - if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) { - mTargetDisplays.add(wc.getDisplayContent()); - addOnTopTasks(wc.getDisplayContent(), mOnTopTasksStart); - } + recordDisplay(wc.getDisplayContent()); if (info.mShowWallpaper) { // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set. final WindowState wallpaper = @@ -535,6 +530,20 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } + private void recordDisplay(DisplayContent dc) { + if (dc == null || mTargetDisplays.contains(dc)) return; + mTargetDisplays.add(dc); + addOnTopTasks(dc, mOnTopTasksStart); + } + + /** + * Records information about the initial task order. This does NOT collect anything. Call this + * before any ordering changes *could* occur, but it is not known yet if it will occur. + */ + void recordTaskOrder(WindowContainer from) { + recordDisplay(from.getDisplayContent()); + } + /** Adds the top non-alwaysOnTop tasks within `task` to `out`. */ private static void addOnTopTasks(Task task, ArrayList<Task> out) { for (int i = task.getChildCount() - 1; i >= 0; --i) { @@ -870,8 +879,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ void finishTransition() { if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) { - Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, - System.identityHashCode(this)); + asyncTraceEnd(System.identityHashCode(this)); } mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos(); mController.mLoggerHandler.post(mLogger::logOnFinish); @@ -892,6 +900,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mFinishingTransition = this; if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) { + // Record all the now-hiding activities so that they are committed after + // recalculating visibilities. We just use mParticipants because we can and it will + // ensure proper reporting of `isInFinishTransition`. + for (int i = 0; i < mTransientHideTasks.size(); ++i) { + mTransientHideTasks.get(i).forAllActivities(r -> { + // Only check leaf-tasks that were collected + if (!mParticipants.contains(r.getTask())) return; + // Only concern ourselves with anything that can become invisible + if (!r.isVisible()) return; + mParticipants.add(r); + }); + } // The transient hide tasks could be occluded now, e.g. returning to home. So trigger // the update to make the activities in the tasks invisible-requested, then the next // step can continue to commit the visibility. @@ -907,6 +927,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final WindowContainer<?> participant = mParticipants.valueAt(i); final ActivityRecord ar = participant.asActivityRecord(); if (ar != null) { + final Task task = ar.getTask(); + if (task == null) continue; boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); // We need both the expected visibility AND current requested-visibility to be // false. If it is expected-visible but not currently visible, it means that @@ -925,9 +947,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (commitVisibility) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Commit activity becoming invisible: %s", ar); - final Task task = ar.getTask(); - if (task != null && !task.isVisibleRequested() - && mTransientLaunches != null) { + if (mTransientLaunches != null && !task.isVisibleRequested()) { // If transition is transient, then snapshots are taken at end of // transition. mController.mSnapshotController.mTaskSnapshotController @@ -941,7 +961,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { enterAutoPip = true; } } - if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) { + final ChangeInfo changeInfo = mChanges.get(ar); + // Due to transient-hide, there may be some activities here which weren't in the + // transition. + if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) { // Legacy dispatch relies on this (for now). ar.mEnteringAnimation = visibleAtTransitionEnd; } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar) @@ -952,8 +975,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Since transient launches don't automatically take focus, make sure we // synchronize focus since we committed to the launch. - if (ar.isTopRunningActivity()) { - ar.moveFocusableActivityToTop("transitionFinished"); + if (!task.isFocused() && ar.isTopRunningActivity()) { + mController.mAtm.setLastResumedActivityUncheckLocked(ar, + "transitionFinished"); } } continue; @@ -1321,8 +1345,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.getTransitionPlayer().onTransitionReady( mToken, info, transaction, mFinishTransaction); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { - Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, - System.identityHashCode(this)); + asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); } } catch (RemoteException e) { // If there's an exception when trying to send the mergedTransaction to the @@ -1597,7 +1620,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" id=" + mSyncId); sb.append(" type=" + transitTypeToString(mType)); - sb.append(" flags=" + mFlags); + sb.append(" flags=0x" + Integer.toHexString(mFlags)); sb.append('}'); return sb.toString(); } @@ -2309,6 +2332,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return isCollecting() && mSyncId >= 0; } + static void asyncTraceBegin(@NonNull String name, int cookie) { + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie); + } + + static void asyncTraceEnd(int cookie) { + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie); + } + @VisibleForTesting static class ChangeInfo { private static final int FLAG_NONE = 0; diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index bcb8c46de5ed..4c1c2ff1ac10 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -37,7 +37,6 @@ import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.Trace; import android.util.ArrayMap; import android.util.Slog; import android.util.TimeUtils; @@ -334,28 +333,6 @@ class TransitionController { return inCollectingTransition(wc) || inPlayingTransition(wc); } - boolean inRecentsTransition(@NonNull WindowContainer wc) { - for (WindowContainer p = wc; p != null; p = p.getParent()) { - // TODO(b/221417431): replace this with deterministic snapshots - if (mCollectingTransition == null) break; - if ((mCollectingTransition.getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0 - && mCollectingTransition.mParticipants.contains(wc)) { - return true; - } - } - - for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { - for (WindowContainer p = wc; p != null; p = p.getParent()) { - // TODO(b/221417431): replace this with deterministic snapshots - if ((mPlayingTransitions.get(i).getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0 - && mPlayingTransitions.get(i).mParticipants.contains(p)) { - return true; - } - } - } - return false; - } - /** @return {@code true} if wc is in a participant subtree */ boolean isTransitionOnDisplay(@NonNull DisplayContent dc) { if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) { @@ -466,26 +443,36 @@ class TransitionController { return type == TRANSIT_OPEN || type == TRANSIT_CLOSE; } - /** Whether the display change should run with blast sync. */ - private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) { - if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) { + /** Sets the sync method for the display change. */ + private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange, + @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) { + final int startRotation = displayChange.getStartRotation(); + final int endRotation = displayChange.getEndRotation(); + if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) { // 180 degrees rotation change may not change screen size. So the clients may draw // some frames before and after the display projection transaction is applied by the // remote player. That may cause some buffers to show in different rotation. So use // sync method to pause clients drawing until the projection transaction is applied. - return true; + mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(), + BLASTSyncEngine.METHOD_BLAST); } final Rect startBounds = displayChange.getStartAbsBounds(); final Rect endBounds = displayChange.getEndAbsBounds(); - if (startBounds == null || endBounds == null) return false; + if (startBounds == null || endBounds == null) return; final int startWidth = startBounds.width(); final int startHeight = startBounds.height(); final int endWidth = endBounds.width(); final int endHeight = endBounds.height(); // This is changing screen resolution. Because the screen decor layers are excluded from // screenshot, their draw transactions need to run with the start transaction. - return (endWidth > startWidth) == (endHeight > startHeight) - && (endWidth != startWidth || endHeight != startHeight); + if ((endWidth > startWidth) == (endHeight > startHeight) + && (endWidth != startWidth || endHeight != startHeight)) { + displayContent.forAllWindows(w -> { + if (w.mToken.mRoundedCornerOverlay && w.mHasSurface) { + w.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; + } + }, true /* traverseTopToBottom */); + } } /** @@ -517,9 +504,9 @@ class TransitionController { } else { newTransition = requestStartTransition(createTransition(type, flags), trigger != null ? trigger.asTask() : null, remoteTransition, displayChange); - if (newTransition != null && displayChange != null && shouldSync(displayChange)) { - mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(), - BLASTSyncEngine.METHOD_BLAST); + if (newTransition != null && displayChange != null && trigger != null + && trigger.asDisplayContent() != null) { + setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent()); } } if (trigger != null) { @@ -577,12 +564,16 @@ class TransitionController { return transition; } - /** Requests transition for a window container which will be removed or invisible. */ - void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) { - if (mTransitionPlayer == null) return; + /** + * Requests transition for a window container which will be removed or invisible. + * @return the new transition if it was created for this request, `null` otherwise. + */ + Transition requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) { + if (mTransitionPlayer == null) return null; + Transition out = null; if (wc.isVisibleRequested()) { if (!isCollecting()) { - requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), + out = requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), wc.asTask(), null /* remoteTransition */, null /* displayChange */); } collectExistenceChange(wc); @@ -591,6 +582,7 @@ class TransitionController { // collecting, this should be a member just in case. collect(wc); } + return out; } /** @see Transition#collect */ @@ -605,6 +597,12 @@ class TransitionController { mCollectingTransition.collectExistenceChange(wc); } + /** @see Transition#recordTaskOrder */ + void recordTaskOrder(@NonNull WindowContainer wc) { + if (mCollectingTransition == null) return; + mCollectingTransition.recordTaskOrder(wc); + } + /** * Collects the window containers which need to be synced with the changing display area into * the current collecting transition. @@ -762,12 +760,12 @@ class TransitionController { // happening in app), so pause task snapshot persisting to not increase the load. mAtm.mWindowManager.mSnapshotController.setPause(true); mAnimatingState = true; - Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0); + Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */); } else if (!animatingState && mAnimatingState) { t.setEarlyWakeupEnd(); mAtm.mWindowManager.mSnapshotController.setPause(false); mAnimatingState = false; - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0); + Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */); } } @@ -886,6 +884,15 @@ class TransitionController { proto.end(token); } + void queueCollecting(Transition transit, Runnable onCollectStart) { + mAtm.mWindowManager.mSyncEngine.queueSyncSet( + // Make sure to collect immediately to prevent another transition + // from sneaking in before it. Note: moveToCollecting internally + // calls startSyncSet. + () -> moveToCollecting(transit), + onCollectStart); + } + /** * This manages the animating state of processes that are running remote animations for * {@link #mTransitionPlayerProc}. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index cd4d6e4f1600..8fecf111d98c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6697,9 +6697,8 @@ public class WindowManagerService extends IWindowManager.Stub mInputManagerCallback.dump(pw, " "); mSnapshotController.dump(pw, " "); - if (mAccessibilityController.hasCallbacks()) { - mAccessibilityController.dump(pw, " "); - } + + dumpAccessibilityController(pw, /* force= */ false); if (dumpAll) { final WindowState imeWindow = mRoot.getCurrentInputMethodWindow(); @@ -6736,6 +6735,23 @@ public class WindowManagerService extends IWindowManager.Stub } } + private void dumpAccessibilityController(PrintWriter pw, boolean force) { + boolean hasCallbacks = mAccessibilityController.hasCallbacks(); + if (!hasCallbacks && !force) { + return; + } + if (!hasCallbacks) { + pw.println("AccessibilityController doesn't have callbacks, but printing it anways:"); + } else { + pw.println("AccessibilityController:"); + } + mAccessibilityController.dump(pw, " "); + } + + private void dumpAccessibilityLocked(PrintWriter pw) { + dumpAccessibilityController(pw, /* force= */ true); + } + private boolean dumpWindows(PrintWriter pw, String name, boolean dumpAll) { final ArrayList<WindowState> windows = new ArrayList(); if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) { @@ -6855,6 +6871,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(" d[isplays]: active display contents"); pw.println(" t[okens]: token list"); pw.println(" w[indows]: window list"); + pw.println(" a11y[accessibility]: accessibility-related state"); pw.println(" package-config: installed packages having app-specific config"); pw.println(" trace: print trace status and write Winscope trace to file"); pw.println(" cmd may also be a NAME to dump windows. NAME may"); @@ -6918,6 +6935,11 @@ public class WindowManagerService extends IWindowManager.Stub dumpWindowsLocked(pw, true, null); } return; + } else if ("accessibility".equals(cmd) || "a11y".equals(cmd)) { + synchronized (mGlobalLock) { + dumpAccessibilityLocked(pw); + } + return; } else if ("all".equals(cmd)) { synchronized (mGlobalLock) { dumpWindowsLocked(pw, true, null); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 32d54d774b40..f63470f2bea4 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -291,23 +291,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (type < 0) { throw new IllegalArgumentException("Can't create transition with no type"); } + transition = new Transition(type, 0 /* flags */, mTransitionController, + mService.mWindowManager.mSyncEngine); // If there is already a collecting transition, queue up a new transition and // return that. The actual start and apply will then be deferred until that // transition starts collecting. This should almost never happen except during // tests. if (mService.mWindowManager.mSyncEngine.hasActiveSync()) { Slog.w(TAG, "startTransition() while one is already collecting."); - final Transition nextTransition = new Transition(type, 0 /* flags */, - mTransitionController, mService.mWindowManager.mSyncEngine); + final Transition nextTransition = transition; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Transition: %s", nextTransition); - mService.mWindowManager.mSyncEngine.queueSyncSet( - // Make sure to collect immediately to prevent another transition - // from sneaking in before it. Note: moveToCollecting internally - // calls startSyncSet. - () -> mTransitionController.moveToCollecting(nextTransition), + mTransitionController.queueCollecting(nextTransition, () -> { nextTransition.start(); + nextTransition.mLogger.mStartWCT = wct; applyTransaction(wct, -1 /*syncId*/, nextTransition, caller); if (needsSetReady) { nextTransition.setAllReady(); @@ -315,7 +313,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub }); return nextTransition.getToken(); } - transition = mTransitionController.createTransition(type); + mTransitionController.moveToCollecting(transition); } if (!transition.isCollecting() && !transition.isForcePlaying()) { Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably" @@ -474,11 +472,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mTransitionController, mService.mWindowManager.mSyncEngine); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Transition for TaskFragment: %s", nextTransition); - mService.mWindowManager.mSyncEngine.queueSyncSet( - // Make sure to collect immediately to prevent another transition - // from sneaking in before it. Note: moveToCollecting internally - // calls startSyncSet. - () -> mTransitionController.moveToCollecting(nextTransition), + mTransitionController.queueCollecting(nextTransition, () -> { if (mTaskFragmentOrganizerController.isValidTransaction(wct) && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller) diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6e3924baadf3..e5a49c3a0ee2 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5647,7 +5647,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean isSyncFinished() { - if (!isVisibleRequested()) { + if (!isVisibleRequested() || isFullyTransparent()) { // Don't wait for invisible windows. However, we don't alter the state in case the // window becomes visible while the sync group is still active. return true; diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index 5c77aa22ece8..19a0c5e8adcb 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -27,7 +27,7 @@ import android.credentials.ui.RequestInfo; import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; -import android.util.Log; +import android.util.Slog; import java.util.ArrayList; import java.util.Set; @@ -67,7 +67,8 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta .createNewSession(mContext, mUserId, providerInfo, this, remoteCredentialService); if (providerClearSession != null) { - Log.i(TAG, "In startProviderSession - provider session created and being added"); + Slog.d(TAG, "In startProviderSession - provider session created " + + "and being added for: " + providerInfo.getComponentName()); mProviders.put(providerClearSession.getComponentName().flattenToString(), providerClearSession); } @@ -77,12 +78,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta @Override // from provider session public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { - Log.i(TAG, "in onStatusChanged with status: " + status); + Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source); if (ProviderSession.isTerminatingStatus(status)) { - Log.i(TAG, "in onStatusChanged terminating status"); + Slog.d(TAG, "in onProviderStatusChanged terminating status"); onProviderTerminated(componentName); } else if (ProviderSession.isCompletionStatus(status)) { - Log.i(TAG, "in onStatusChanged isCompletionStatus status"); + Slog.d(TAG, "in onProviderStatusChanged isCompletionStatus status"); onProviderResponseComplete(componentName); } } diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 02aaf867fa7b..a04143afadcd 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -33,7 +33,7 @@ import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; -import android.util.Log; +import android.util.Slog; import com.android.server.credentials.metrics.ProviderStatusForMetrics; @@ -77,7 +77,8 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR .createNewSession(mContext, mUserId, providerInfo, this, remoteCredentialService); if (providerCreateSession != null) { - Log.i(TAG, "In startProviderSession - provider session created and being added"); + Slog.d(TAG, "In initiateProviderSession - provider session created and " + + "being added for: " + providerInfo.getComponentName()); mProviders.put(providerCreateSession.getComponentName().flattenToString(), providerCreateSession); } @@ -120,7 +121,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onFinalResponseReceived(ComponentName componentName, @Nullable CreateCredentialResponse response) { - Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString()); + Slog.d(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get( componentName.flattenToString()).mProviderSessionMetric @@ -163,13 +164,13 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { - Log.i(TAG, "in onProviderStatusChanged with status: " + status); + Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source); // If all provider responses have been received, we can either need the UI, // or we need to respond with error. The only other case is the entry being // selected after the UI has been invoked which has a separate code path. if (!isAnyProviderPending()) { if (isUiInvocationNeeded()) { - Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded"); + Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded"); getProviderDataAndInitiateUi(); } else { respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index c44e665ba699..208a4be9a70e 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -30,7 +30,7 @@ import android.credentials.ui.RequestInfo; import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; -import android.util.Log; +import android.util.Slog; import com.android.server.credentials.metrics.ProviderStatusForMetrics; @@ -77,7 +77,8 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, .createNewSession(mContext, mUserId, providerInfo, this, remoteCredentialService); if (providerGetSession != null) { - Log.i(TAG, "In startProviderSession - provider session created and being added"); + Slog.d(TAG, "In startProviderSession - provider session created and " + + "being added for: " + providerInfo.getComponentName()); mProviders.put(providerGetSession.getComponentName().flattenToString(), providerGetSession); } @@ -116,7 +117,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onFinalResponseReceived(ComponentName componentName, @Nullable GetCredentialResponse response) { - Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString()); + Slog.d(TAG, "onFinalResponseReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); mRequestSessionMetric.collectChosenMetricViaCandidateTransfer( mProviders.get(componentName.flattenToString()) @@ -160,7 +161,8 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { - Log.i(TAG, "in onStatusChanged with status: " + status + "and source: " + source); + Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: " + + status + ", and source: " + source); // Auth entry was selected, and it did not have any underlying credentials if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { @@ -173,7 +175,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, // or we need to respond with error. The only other case is the entry being // selected after the UI has been invoked which has a separate code path. if (isUiInvocationNeeded()) { - Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded"); + Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded"); getProviderDataAndInitiateUi(); } else { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java index f274e65a20c3..9e7a87e74522 100644 --- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java @@ -33,7 +33,6 @@ import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; -import android.util.Log; import android.util.Slog; import java.util.ArrayList; @@ -67,6 +66,9 @@ public class PrepareGetRequestSession extends GetRequestSession { @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { + Slog.d(TAG, "in onProviderStatusChanged with status: " + status + ", and " + + "source: " + source); + switch (source) { case REMOTE_PROVIDER: // Remote provider's status changed. We should check if all providers are done, and @@ -123,7 +125,7 @@ public class PrepareGetRequestSession extends GetRequestSession { hasPermission, credentialTypes, hasAuthenticationResults, hasRemoteResults, uiIntent)); } catch (RemoteException e) { - Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e); + Slog.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e); } } @@ -138,7 +140,7 @@ public class PrepareGetRequestSession extends GetRequestSession { /*hasRemoteResults=*/ false, /*pendingIntent=*/ null)); } catch (RemoteException e) { - Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e); + Slog.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e); } } @@ -179,10 +181,8 @@ public class PrepareGetRequestSession extends GetRequestSession { private PendingIntent getUiIntent() { ArrayList<ProviderData> providerDataList = new ArrayList<>(); for (ProviderSession session : mProviders.values()) { - Log.i(TAG, "preparing data for : " + session.getComponentName()); ProviderData providerData = session.prepareUiData(); if (providerData != null) { - Log.i(TAG, "Provider data is not null"); providerDataList.add(providerData); } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index 0c3d3f4ed6d2..9ec0ecd93b3c 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -26,7 +26,6 @@ import android.credentials.ui.ProviderPendingIntentResponse; import android.os.ICancellationSignal; import android.service.credentials.CallingAppInfo; import android.service.credentials.ClearCredentialStateRequest; -import android.util.Log; import android.util.Slog; /** @@ -81,7 +80,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS @Override public void onProviderResponseSuccess(@Nullable Void response) { - Log.i(TAG, "in onProviderResponseSuccess"); + Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName); mProviderResponseSet = true; updateStatusAndInvokeCallback(Status.COMPLETE, /*source=*/ CredentialsSource.REMOTE_PROVIDER); @@ -105,7 +104,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS updateStatusAndInvokeCallback(Status.SERVICE_DEAD, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - Slog.i(TAG, "Component names different in onProviderServiceDied - " + Slog.w(TAG, "Component names different in onProviderServiceDied - " + "this should not happen"); } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index 8b9255a9c56b..09433dbb0c52 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -37,7 +37,6 @@ import android.service.credentials.CreateCredentialRequest; import android.service.credentials.CreateEntry; import android.service.credentials.CredentialProviderService; import android.service.credentials.RemoteEntry; -import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -93,7 +92,8 @@ public final class ProviderCreateSession extends ProviderSession< createRequestSession.mHybridService ); } - Log.i(TAG, "Unable to create provider session"); + Slog.d(TAG, "Unable to create provider session for: " + + providerInfo.getComponentName()); return null; } @@ -122,7 +122,6 @@ public final class ProviderCreateSession extends ProviderSession< return new CreateCredentialRequest(callingAppInfo, capability, clientRequest.getCredentialData()); } - Log.i(TAG, "Unable to create provider request - capabilities do not match"); return null; } @@ -146,7 +145,7 @@ public final class ProviderCreateSession extends ProviderSession< @Override public void onProviderResponseSuccess( @Nullable BeginCreateCredentialResponse response) { - Log.i(TAG, "in onProviderResponseSuccess"); + Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName); onSetInitialRemoteResponse(response); } @@ -169,7 +168,7 @@ public final class ProviderCreateSession extends ProviderSession< updateStatusAndInvokeCallback(Status.SERVICE_DEAD, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - Slog.i(TAG, "Component names different in onProviderServiceDied - " + Slog.w(TAG, "Component names different in onProviderServiceDied - " + "this should not happen"); } } @@ -180,7 +179,6 @@ public final class ProviderCreateSession extends ProviderSession< } private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) { - Log.i(TAG, "onSetInitialRemoteResponse with save entries"); mProviderResponse = response; mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(), response.getRemoteCreateEntry()); @@ -199,14 +197,12 @@ public final class ProviderCreateSession extends ProviderSession< @Nullable protected CreateCredentialProviderData prepareUiData() throws IllegalArgumentException { - Log.i(TAG, "In prepareUiData"); if (!ProviderSession.isUiInvokingStatus(getStatus())) { - Log.i(TAG, "In prepareUiData not in uiInvokingStatus"); + Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString()); return null; } if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) { - Log.i(TAG, "In prepareUiData save entries not null"); return mProviderResponseDataHandler.toCreateCredentialProviderData(); } return null; @@ -218,7 +214,7 @@ public final class ProviderCreateSession extends ProviderSession< switch (entryType) { case SAVE_ENTRY_KEY: if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) { - Log.i(TAG, "Unexpected save entry key"); + Slog.w(TAG, "Unexpected save entry key"); invokeCallbackOnInternalInvalidState(); return; } @@ -226,14 +222,14 @@ public final class ProviderCreateSession extends ProviderSession< break; case REMOTE_ENTRY_KEY: if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) { - Log.i(TAG, "Unexpected remote entry key"); + Slog.w(TAG, "Unexpected remote entry key"); invokeCallbackOnInternalInvalidState(); return; } onRemoteEntrySelected(providerPendingIntentResponse); break; default: - Log.i(TAG, "Unsupported entry type selected"); + Slog.w(TAG, "Unsupported entry type selected"); invokeCallbackOnInternalInvalidState(); } } @@ -268,7 +264,7 @@ public final class ProviderCreateSession extends ProviderSession< if (credentialResponse != null) { mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse); } else { - Log.i(TAG, "onSaveEntrySelected - no response or error found in pending " + Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending " + "intent response"); invokeCallbackOnInternalInvalidState(); } @@ -284,14 +280,14 @@ public final class ProviderCreateSession extends ProviderSession< private CreateCredentialException maybeGetPendingIntentException( ProviderPendingIntentResponse pendingIntentResponse) { if (pendingIntentResponse == null) { - Log.i(TAG, "pendingIntentResponse is null"); + Slog.w(TAG, "pendingIntentResponse is null"); return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS); } if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { CreateCredentialException exception = PendingIntentResultHandler .extractCreateCredentialException(pendingIntentResponse.getResultData()); if (exception != null) { - Log.i(TAG, "Pending intent contains provider exception"); + Slog.d(TAG, "Pending intent contains provider exception"); return exception; } } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { @@ -343,7 +339,7 @@ public final class ProviderCreateSession extends ProviderSession< public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) { if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) { - Log.i(TAG, "Remote entry being dropped as it does not meet the restriction" + Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction" + "checks."); return; } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 8d3d06469944..0c2b5633d501 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -115,7 +115,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential getRequestSession.mHybridService ); } - Log.i(TAG, "Unable to create provider session"); + Slog.d(TAG, "Unable to create provider session for: " + + providerInfo.getComponentName()); return null; } @@ -146,17 +147,15 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential android.credentials.GetCredentialRequest clientRequest, CredentialProviderInfo info ) { + Slog.d(TAG, "Filtering request options for: " + info.getComponentName()); List<CredentialOption> filteredOptions = new ArrayList<>(); for (CredentialOption option : clientRequest.getCredentialOptions()) { if (providerCapabilities.contains(option.getType()) && isProviderAllowed(option, info.getComponentName()) && checkSystemProviderRequirement(option, info.isSystemProvider())) { - Log.i(TAG, "In createProviderRequest - capability found : " - + option.getType()); + Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering" + + "conditions"); filteredOptions.add(option); - } else { - Log.i(TAG, "In createProviderRequest - capability not " - + "found, or provider not allowed : " + option.getType()); } } if (!filteredOptions.isEmpty()) { @@ -165,15 +164,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential .setCredentialOptions( filteredOptions).build(); } - Log.i(TAG, "In createProviderRequest - returning null"); + Slog.d(TAG, "No options filtered"); return null; } private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) { if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains( componentName)) { - Log.d(TAG, "Provider allow list specified but does not contain this provider: " - + componentName.flattenToString()); + Slog.d(TAG, "Provider allow list specified but does not contain this provider"); return false; } return true; @@ -182,7 +180,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential private static boolean checkSystemProviderRequirement(CredentialOption option, boolean isSystemProvider) { if (option.isSystemProviderRequired() && !isSystemProvider) { - Log.d(TAG, "System provider required, but this service is not a system provider"); + Slog.d(TAG, "System provider required, but this service is not a system provider"); return false; } return true; @@ -210,6 +208,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential /** Called when the provider response has been updated by an external source. */ @Override // Callback from the remote provider public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) { + Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName); onSetInitialRemoteResponse(response); } @@ -231,7 +230,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential updateStatusAndInvokeCallback(Status.SERVICE_DEAD, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - Slog.i(TAG, "Component names different in onProviderServiceDied - " + Slog.w(TAG, "Component names different in onProviderServiceDied - " + "this should not happen"); } } @@ -244,13 +243,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential @Override // Selection call from the request provider protected void onUiEntrySelected(String entryType, String entryKey, ProviderPendingIntentResponse providerPendingIntentResponse) { - Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey); + Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: " + + entryKey); switch (entryType) { case CREDENTIAL_ENTRY_KEY: CredentialEntry credentialEntry = mProviderResponseDataHandler .getCredentialEntry(entryKey); if (credentialEntry == null) { - Log.i(TAG, "Unexpected credential entry key"); + Slog.w(TAG, "Unexpected credential entry key"); invokeCallbackOnInternalInvalidState(); return; } @@ -259,7 +259,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential case ACTION_ENTRY_KEY: Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey); if (actionEntry == null) { - Log.i(TAG, "Unexpected action entry key"); + Slog.w(TAG, "Unexpected action entry key"); invokeCallbackOnInternalInvalidState(); return; } @@ -269,21 +269,21 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential Action authenticationEntry = mProviderResponseDataHandler .getAuthenticationAction(entryKey); if (authenticationEntry == null) { - Log.i(TAG, "Unexpected authenticationEntry key"); + Slog.w(TAG, "Unexpected authenticationEntry key"); invokeCallbackOnInternalInvalidState(); return; } boolean additionalContentReceived = onAuthenticationEntrySelected(providerPendingIntentResponse); if (additionalContentReceived) { - Log.i(TAG, "Additional content received - removing authentication entry"); + Slog.d(TAG, "Additional content received - removing authentication entry"); mProviderResponseDataHandler.removeAuthenticationAction(entryKey); if (!mProviderResponseDataHandler.isEmptyResponse()) { updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED, /*source=*/ CredentialsSource.AUTH_ENTRY); } } else { - Log.i(TAG, "Additional content not received"); + Slog.d(TAG, "Additional content not received from authentication entry"); mProviderResponseDataHandler .updateAuthEntryWithNoCredentialsReceived(entryKey); updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY, @@ -294,12 +294,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) { onRemoteEntrySelected(providerPendingIntentResponse); } else { - Log.i(TAG, "Unexpected remote entry key"); + Slog.d(TAG, "Unexpected remote entry key"); invokeCallbackOnInternalInvalidState(); } break; default: - Log.i(TAG, "Unsupported entry type selected"); + Slog.w(TAG, "Unsupported entry type selected"); invokeCallbackOnInternalInvalidState(); } } @@ -320,26 +320,24 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential @Override // Call from request session to data to be shown on the UI @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException { - Log.i(TAG, "In prepareUiData"); if (!ProviderSession.isUiInvokingStatus(getStatus())) { - Log.i(TAG, "In prepareUiData - provider does not want to show UI: " - + mComponentName.flattenToString()); + Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString()); return null; } if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) { return mProviderResponseDataHandler.toGetCredentialProviderData(); } - Log.i(TAG, "In prepareUiData response null"); + Slog.d(TAG, "In prepareUiData response null"); return null; } - private Intent setUpFillInIntent(@NonNull String id) { + private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) { // TODO: Determine if we should skip this entry if entry id is not set, or is set // but does not resolve to a valid option. For now, not skipping it because // it may be possible that the provider adds their own extras and expects to receive // those and complete the flow. if (mBeginGetOptionToCredentialOptionMap.get(id) == null) { - Log.i(TAG, "Id from Credential Entry does not resolve to a valid option"); + Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option"); return new Intent(); } return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST, @@ -382,7 +380,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential getCredentialResponse); return; } - Log.i(TAG, "Pending intent response contains no credential, or error"); + Slog.d(TAG, "Pending intent response contains no credential, or error " + + "for a credential entry"); invokeCallbackOnInternalInvalidState(); } @@ -390,14 +389,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential private GetCredentialException maybeGetPendingIntentException( ProviderPendingIntentResponse pendingIntentResponse) { if (pendingIntentResponse == null) { - Log.i(TAG, "pendingIntentResponse is null"); return null; } if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { GetCredentialException exception = PendingIntentResultHandler .extractGetCredentialException(pendingIntentResponse.getResultData()); if (exception != null) { - Log.i(TAG, "Pending intent contains provider exception"); return exception; } } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { @@ -463,7 +460,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential /** Returns true if either an exception or a response is found. */ private void onActionEntrySelected(ProviderPendingIntentResponse providerPendingIntentResponse) { - Log.i(TAG, "onActionEntrySelected"); + Slog.d(TAG, "onActionEntrySelected"); onCredentialEntrySelected(providerPendingIntentResponse); } @@ -559,7 +556,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential String id = generateUniqueId(); Entry entry = new Entry(CREDENTIAL_ENTRY_KEY, id, credentialEntry.getSlice(), - setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId())); + setUpFillInIntentWithFinalRequest(credentialEntry + .getBeginGetCredentialOptionId())); mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry)); mCredentialEntryTypes.add(credentialEntry.getType()); } @@ -574,9 +572,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential public void addAuthenticationAction(Action authenticationAction, @AuthenticationEntry.Status int status) { - Log.i(TAG, "In addAuthenticationAction"); String id = generateUniqueId(); - Log.i(TAG, "In addAuthenticationAction, id : " + id); AuthenticationEntry entry = new AuthenticationEntry( AUTHENTICATION_ACTION_ENTRY_KEY, id, authenticationAction.getSlice(), @@ -591,7 +587,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) { if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) { - Log.i(TAG, "Remote entry being dropped as it does not meet the restriction" + Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction" + " checks."); return; } @@ -715,7 +711,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT) .findFirst(); if (previousMostRecentAuthEntry.isEmpty()) { - Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found"); return; } String id = previousMostRecentAuthEntry.get().getKey(); diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index 24292ef2cdab..c10f5640c466 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -33,7 +33,7 @@ import android.os.ICancellationSignal; import android.service.credentials.CallingAppInfo; import android.service.credentials.CredentialEntry; import android.service.credentials.CredentialProviderService; -import android.telecom.Log; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -145,14 +145,12 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption private List<Entry> prepareUiCredentialEntries( @NonNull List<CredentialEntry> credentialEntries) { - Log.i(TAG, "in prepareUiProviderDataWithCredentials"); List<Entry> credentialUiEntries = new ArrayList<>(); // Populate the credential entries for (CredentialEntry credentialEntry : credentialEntries) { String entryId = generateUniqueId(); mUiCredentialEntries.put(entryId, credentialEntry); - Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId); credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId, credentialEntry.getSlice(), setUpFillInIntent())); @@ -172,15 +170,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption @Override protected ProviderData prepareUiData() { - Log.i(TAG, "In prepareUiData"); if (!ProviderSession.isUiInvokingStatus(getStatus())) { - Log.i(TAG, "In prepareUiData - provider does not want to show UI: " - + mComponentName.flattenToString()); + Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString()); return null; } if (mProviderResponse == null) { - Log.i(TAG, "In prepareUiData response null"); - throw new IllegalStateException("Response must be in completion mode"); + Slog.w(TAG, "In prepareUiData but response is null. This is strange."); + return null; } return new GetCredentialProviderData.Builder( mComponentName.flattenToString()).setActionChips(null) @@ -200,13 +196,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption case CREDENTIAL_ENTRY_KEY: CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey); if (credentialEntry == null) { - Log.i(TAG, "Unexpected credential entry key"); + Slog.w(TAG, "Unexpected credential entry key"); return; } onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse); break; default: - Log.i(TAG, "Unsupported entry type selected"); + Slog.w(TAG, "Unsupported entry type selected"); } } @@ -233,10 +229,8 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption } return; } - - Log.i(TAG, "Pending intent response contains no credential, or error"); } - Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result"); + Slog.w(TAG, "CredentialEntry does not have a credential or a pending intent result"); } @Override @@ -279,14 +273,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption protected GetCredentialException maybeGetPendingIntentException( ProviderPendingIntentResponse pendingIntentResponse) { if (pendingIntentResponse == null) { - android.util.Log.i(TAG, "pendingIntentResponse is null"); return null; } if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { GetCredentialException exception = PendingIntentResultHandler .extractGetCredentialException(pendingIntentResponse.getResultData()); if (exception != null) { - android.util.Log.i(TAG, "Pending intent contains provider exception"); + Slog.d(TAG, "Pending intent contains provider exception"); return exception; } } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index d165756b3811..d02a8c1ee510 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -29,7 +29,6 @@ import android.credentials.ui.ProviderData; import android.credentials.ui.ProviderPendingIntentResponse; import android.os.ICancellationSignal; import android.os.RemoteException; -import android.util.Log; import android.util.Slog; import com.android.server.credentials.metrics.ProviderSessionMetric; @@ -253,7 +252,7 @@ public abstract class ProviderSession<T, R> @Nullable ComponentName expectedRemoteEntryProviderService) { // Check if the service is the one set by the OEM. If not silently reject this entry if (!mComponentName.equals(expectedRemoteEntryProviderService)) { - Log.i(TAG, "Remote entry being dropped as it is not from the service " + Slog.w(TAG, "Remote entry being dropped as it is not from the service " + "configured by the OEM."); return false; } @@ -270,15 +269,12 @@ public abstract class ProviderSession<T, R> return true; } } catch (SecurityException e) { - Log.i(TAG, "Error getting info for " - + mComponentName.flattenToString() + ": " + e.getMessage()); + Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e); return false; } catch (PackageManager.NameNotFoundException e) { - Log.i(TAG, "Error getting info for " - + mComponentName.flattenToString() + ": " + e.getMessage()); + Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e); return false; } - Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail"); return false; } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index e98c5241ae00..8fd02691e190 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -32,7 +32,6 @@ import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.service.credentials.CallingAppInfo; -import android.util.Log; import android.util.Slog; import com.android.internal.R; @@ -179,7 +178,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential @Override // from CredentialManagerUiCallbacks public void onUiSelection(UserSelectionDialogResult selection) { if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { - Log.i(TAG, "Request has already been completed. This is strange."); + Slog.w(TAG, "Request has already been completed. This is strange."); return; } if (isSessionCancelled()) { @@ -187,13 +186,11 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential return; } String providerId = selection.getProviderId(); - Log.i(TAG, "onUiSelection, providerId: " + providerId); ProviderSession providerSession = mProviders.get(providerId); if (providerSession == null) { - Log.i(TAG, "providerSession not found in onUiSelection"); + Slog.w(TAG, "providerSession not found in onUiSelection. This is strange."); return; } - Log.i(TAG, "Provider session found"); mRequestSessionMetric.collectMetricPerBrowsingSelect(selection, providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric()); providerSession.onUiEntrySelected(selection.getEntryKey(), @@ -247,15 +244,13 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential void getProviderDataAndInitiateUi() { ArrayList<ProviderData> providerDataList = getProviderDataForUi(); if (!providerDataList.isEmpty()) { - Log.i(TAG, "provider list not empty about to initiate ui"); launchUiWithProviderData(providerDataList); } } @NonNull protected ArrayList<ProviderData> getProviderDataForUi() { - Log.i(TAG, "In getProviderDataAndInitiateUi"); - Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size()); + Slog.d(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size()); ArrayList<ProviderData> providerDataList = new ArrayList<>(); mRequestSessionMetric.logCandidatePhaseMetrics(mProviders); @@ -265,10 +260,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } for (ProviderSession session : mProviders.values()) { - Log.i(TAG, "preparing data for : " + session.getComponentName()); ProviderData providerData = session.prepareUiData(); if (providerData != null) { - Log.i(TAG, "Provider data is not null"); providerDataList.add(providerData); } } @@ -284,7 +277,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false, ProviderStatusForMetrics.FINAL_SUCCESS); if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { - Log.i(TAG, "Request has already been completed. This is strange."); + Slog.w(TAG, "Request has already been completed. This is strange."); return; } if (isSessionCancelled()) { @@ -300,7 +293,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } catch (RemoteException e) { mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); - Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage()); + Slog.e(TAG, "Issue while responding to client with a response : " + e); mRequestSessionMetric.logApiCalledAtFinish( /*apiStatus=*/ ApiStatus.FAILURE.getMetricCode()); } @@ -317,7 +310,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { - Log.i(TAG, "Request has already been completed. This is strange."); + Slog.w(TAG, "Request has already been completed. This is strange."); return; } if (isSessionCancelled()) { @@ -330,7 +323,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential try { invokeClientCallbackError(errorType, errorMsg); } catch (RemoteException e) { - Log.i(TAG, "Issue while responding to client with error : " + e.getMessage()); + Slog.e(TAG, "Issue while responding to client with error : " + e); } boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING); mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index f111a9541303..926c7e400144 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -27,6 +27,7 @@ import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PAREN import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.BroadcastOptions; import android.app.admin.DevicePolicyIdentifiers; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyState; @@ -353,6 +354,7 @@ final class DevicePolicyEngine { policyDefinition, userId); } + sendDevicePolicyChangedToSystem(userId); } /** @@ -478,6 +480,8 @@ final class DevicePolicyEngine { enforcingAdmin, policyDefinition, UserHandle.USER_ALL); + + sendDevicePolicyChangedToSystem(UserHandle.USER_ALL); } /** @@ -699,7 +703,7 @@ final class DevicePolicyEngine { if (policyDefinition.isGlobalOnlyPolicy()) { throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only" - + "policy."); + + " policy."); } if (!mLocalPolicies.contains(userId)) { @@ -724,7 +728,7 @@ final class DevicePolicyEngine { private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) { if (policyDefinition.isLocalOnlyPolicy()) { throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only" - + "policy."); + + " policy."); } if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) { @@ -761,6 +765,20 @@ final class DevicePolicyEngine { policyValue == null ? null : policyValue.getValue(), mContext, userId); } + private void sendDevicePolicyChangedToSystem(int userId) { + Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + Bundle options = new BroadcastOptions() + .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) + .toBundle(); + Binder.withCleanCallingIdentity(() -> mContext.sendBroadcastAsUser( + intent, + new UserHandle(userId), + /* receiverPermissions= */ null, + options)); + } + private <V> void sendPolicyResultToAdmin( EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int result, int userId) { Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_SET_RESULT); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 94e6e732dd76..29f9a306880f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.LOCK_DEVICE; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; +import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL; @@ -59,6 +60,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTI import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT; @@ -441,7 +443,6 @@ import android.util.AtomicFile; import android.util.DebugUtils; import android.util.IndentingPrintWriter; import android.util.IntArray; -import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -7767,12 +7768,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Explicit behaviour if (factoryReset) { // TODO(b/254031494) Replace with new factory reset permission checks - boolean hasPermission = isDeviceOwnerUserId(userId) - || (isOrganizationOwnedDeviceWithManagedProfile() - && calledOnParentInstance); - Preconditions.checkState(hasPermission, - "Admin %s does not have permission to factory reset the device.", - userId); + if (!isPermissionCheckFlagEnabled()) { + boolean hasPermission = isDeviceOwnerUserId(userId) + || (isOrganizationOwnedDeviceWithManagedProfile() + && calledOnParentInstance); + Preconditions.checkCallAuthorization(hasPermission, + "Admin %s does not have permission to factory reset the device.", + userId); + } wipeDevice = true; } else { Preconditions.checkCallAuthorization(!isSystemUser, @@ -13131,19 +13134,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } int userId = caller.getUserId(); - if (!UserRestrictionsUtils.isValidRestriction(key)) { - return; - } checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION); if (isPolicyEngineForFinanceFlagEnabled()) { - int affectedUserId = parent ? getProfileParentId(userId) : userId; - EnforcingAdmin admin = enforcePermissionForUserRestriction( - who, - key, - caller.getPackageName(), - affectedUserId); - if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) { + if (!isDeviceOwner(caller) && !isProfileOwner(caller)) { + if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) { + throw new IllegalStateException("Calling package is not targeting Android U."); + } + if (!UserRestrictionsUtils.isValidRestriction(key)) { + throw new IllegalArgumentException("Invalid restriction key: " + key); + } + int affectedUserId = parent ? getProfileParentId(userId) : userId; + EnforcingAdmin admin = enforcePermissionForUserRestriction( + who, + key, + caller.getPackageName(), + affectedUserId); PolicyDefinition<Boolean> policyDefinition = PolicyDefinition.getPolicyDefinitionForUserRestriction(key); if (enabledFromThisOwner) { @@ -13155,7 +13161,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { setGlobalUserRestrictionInternal(admin, key, /* enabled= */ false); } if (!policyDefinition.isGlobalOnlyPolicy()) { - setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, userId); + setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, + userId); int parentUserId = getProfileParentId(userId); if (parentUserId != userId) { @@ -13165,49 +13172,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } else { + if (!UserRestrictionsUtils.isValidRestriction(key)) { + return; + } + Objects.requireNonNull(who, "ComponentName is null"); + EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage); + checkAdminCanSetRestriction(caller, parent, key); setBackwardCompatibleUserRestriction( caller, admin, key, enabledFromThisOwner, parent); } } else { - Objects.requireNonNull(who, "ComponentName is null"); - if (parent) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller)); - } else { - Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwner(caller)); - } - synchronized (getLockObject()) { - if (isDefaultDeviceOwner(caller)) { - if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) { - throw new SecurityException("Device owner cannot set user restriction " - + key); - } - Preconditions.checkArgument(!parent, - "Cannot use the parent instance in Device Owner mode"); - } else if (isFinancedDeviceOwner(caller)) { - if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) { - throw new SecurityException("Cannot set user restriction " + key - + " when managing a financed device"); - } - Preconditions.checkArgument(!parent, - "Cannot use the parent instance in Financed Device Owner" - + " mode"); - } else { - boolean profileOwnerCanChangeOnItself = !parent - && UserRestrictionsUtils.canProfileOwnerChange( - key, userId == getMainUserId()); - boolean orgOwnedProfileOwnerCanChangeGlobally = parent - && isProfileOwnerOfOrganizationOwnedDevice(caller) - && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange( - key); - - if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) { - throw new SecurityException("Profile owner cannot set user restriction " - + key); - } - } + if (!UserRestrictionsUtils.isValidRestriction(key)) { + return; } + Objects.requireNonNull(who, "ComponentName is null"); + checkAdminCanSetRestriction(caller, parent, key); synchronized (getLockObject()) { final ActiveAdmin activeAdmin = getParentOfAdminIfRequired( getProfileOwnerOrDeviceOwnerLocked(userId), parent); @@ -13224,6 +13203,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { logUserRestrictionCall(key, enabledFromThisOwner, parent, caller); } + private void checkAdminCanSetRestriction(CallerIdentity caller, boolean parent, String key) { + if (parent) { + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller)); + } else { + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwner(caller)); + } + synchronized (getLockObject()) { + if (isDefaultDeviceOwner(caller)) { + if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) { + throw new SecurityException("Device owner cannot set user restriction " + + key); + } + Preconditions.checkArgument(!parent, + "Cannot use the parent instance in Device Owner mode"); + } else if (isFinancedDeviceOwner(caller)) { + if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) { + throw new SecurityException("Cannot set user restriction " + key + + " when managing a financed device"); + } + Preconditions.checkArgument(!parent, + "Cannot use the parent instance in Financed Device Owner" + + " mode"); + } else { + boolean profileOwnerCanChangeOnItself = !parent + && UserRestrictionsUtils.canProfileOwnerChange( + key, caller.getUserId() == getMainUserId()); + boolean orgOwnedProfileOwnerCanChangeGlobally = parent + && isProfileOwnerOfOrganizationOwnedDevice(caller) + && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange( + key); + + if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) { + throw new SecurityException("Profile owner cannot set user restriction " + + key); + } + } + } + } private void setBackwardCompatibleUserRestriction( CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled, boolean parent) { @@ -13252,20 +13271,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setUserRestrictionGlobally(String callerPackage, String key) { final CallerIdentity caller = getCallerIdentity(callerPackage); - if (!UserRestrictionsUtils.isValidRestriction(key)) { - return; - } checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION); if (!isPolicyEngineForFinanceFlagEnabled()) { throw new IllegalStateException("Feature flag is not enabled."); } - + if (isDeviceOwner(caller) || isProfileOwner(caller)) { + throw new IllegalStateException("Admins are not allowed to call this API."); + } if (!mInjector.isChangeEnabled( ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) { throw new IllegalStateException("Calling package is not targeting Android U."); } + if (!UserRestrictionsUtils.isValidRestriction(key)) { + throw new IllegalArgumentException("Invalid restriction key: " + key); + } EnforcingAdmin admin = enforcePermissionForUserRestriction( /* who= */ null, @@ -13416,14 +13437,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int targetUserId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId(); EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage); - Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId); - // Add global restrictions set by the admin as well if admin is not targeting Android U. - if (!mInjector.isChangeEnabled( - ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) { + if (isDeviceOwner(caller) || isProfileOwner(caller)) { + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isFinancedDeviceOwner(caller) + || isProfileOwner(caller) + || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller))); + + Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId); + // Add global restrictions set by the admin as well. restrictions.putAll( getUserRestrictionsFromPolicyEngine(admin, UserHandle.USER_ALL)); + return restrictions; + } else { + if (!mInjector.isChangeEnabled( + ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) { + throw new IllegalStateException("Calling package is not targeting Android U."); + } + return getUserRestrictionsFromPolicyEngine(admin, targetUserId); } - return restrictions; } else { Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) @@ -13439,164 +13471,162 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // Map of user restriction to permission. - private static final HashMap<String, String> USER_RESTRICTION_PERMISSIONS = new HashMap<>(); + private static final HashMap<String, String[]> USER_RESTRICTION_PERMISSIONS = new HashMap<>(); { USER_RESTRICTION_PERMISSIONS.put( - UserManager.ENSURE_VERIFY_APPS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES); + UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, new String[]{MANAGE_DEVICE_POLICY_PROFILES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_WIFI_TETHERING, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_WIFI_DIRECT, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_USER_SWITCH, MANAGE_DEVICE_POLICY_USERS); + UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_USB_FILE_TRANSFER, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER); + UserManager.DISALLOW_ADJUST_VOLUME, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_UNMUTE_MICROPHONE, MANAGE_DEVICE_POLICY_MICROPHONE); + UserManager.DISALLOW_AIRPLANE_MODE, new String[]{MANAGE_DEVICE_POLICY_AIRPLANE_MODE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_UNMUTE_DEVICE, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT); + UserManager.DISALLOW_AMBIENT_DISPLAY, new String[]{MANAGE_DEVICE_POLICY_DISPLAY}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_UNINSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL); + UserManager.DISALLOW_APPS_CONTROL, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_UNIFIED_PASSWORD, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS); + UserManager.DISALLOW_AUTOFILL, new String[]{MANAGE_DEVICE_POLICY_AUTOFILL}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS); + UserManager.DISALLOW_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SMS, MANAGE_DEVICE_POLICY_SMS); + UserManager.DISALLOW_BLUETOOTH_SHARING, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_CAMERA, new String[]{MANAGE_DEVICE_POLICY_CAMERA}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SHARE_LOCATION, MANAGE_DEVICE_POLICY_LOCATION); + UserManager.DISALLOW_CAMERA_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_CAMERA_TOGGLE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION); + UserManager.DISALLOW_CELLULAR_2G, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SET_WALLPAPER, MANAGE_DEVICE_POLICY_WALLPAPER); + UserManager.DISALLOW_CHANGE_WIFI_STATE, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SET_USER_ICON, MANAGE_DEVICE_POLICY_USERS); + UserManager.DISALLOW_CONFIG_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SAFE_BOOT, MANAGE_DEVICE_POLICY_SAFE_BOOT); + UserManager.DISALLOW_CONFIG_BRIGHTNESS, new String[]{MANAGE_DEVICE_POLICY_DISPLAY}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_RUN_IN_BACKGROUND, MANAGE_DEVICE_POLICY_SAFE_BOOT); + UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_REMOVE_USER, MANAGE_DEVICE_POLICY_USERS); + UserManager.DISALLOW_CONFIG_CREDENTIALS, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_PRINTING, MANAGE_DEVICE_POLICY_PRINTING); + UserManager.DISALLOW_CONFIG_DATE_TIME, new String[]{MANAGE_DEVICE_POLICY_TIME}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_OUTGOING_CALLS, MANAGE_DEVICE_POLICY_CALLS); + UserManager.DISALLOW_CONFIG_DEFAULT_APPS, new String[]{MANAGE_DEFAULT_APPLICATIONS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_OUTGOING_BEAM, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION); + UserManager.DISALLOW_CONFIG_LOCALE, new String[]{MANAGE_DEVICE_POLICY_LOCALE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_NETWORK_RESET, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_CONFIG_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA); + UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_MODIFY_ACCOUNTS, MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT); + UserManager.DISALLOW_CONFIG_PRIVATE_DNS, new String[]{MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_MICROPHONE_TOGGLE, MANAGE_DEVICE_POLICY_MICROPHONE); + UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, new String[]{MANAGE_DEVICE_POLICY_DISPLAY}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, - MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES); + UserManager.DISALLOW_CONFIG_TETHERING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, - MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES); + UserManager.DISALLOW_CONFIG_VPN, new String[]{MANAGE_DEVICE_POLICY_VPN}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_INSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL); + UserManager.DISALLOW_CONFIG_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_FUN, MANAGE_DEVICE_POLICY_FUN); + UserManager.DISALLOW_CONTENT_CAPTURE, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_FACTORY_RESET, MANAGE_DEVICE_POLICY_FACTORY_RESET); + UserManager.DISALLOW_CONTENT_SUGGESTIONS, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES); + UserManager.DISALLOW_CREATE_WINDOWS, new String[]{MANAGE_DEVICE_POLICY_WINDOWS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_DATA_ROAMING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION); + UserManager.DISALLOW_DATA_ROAMING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CREATE_WINDOWS, MANAGE_DEVICE_POLICY_WINDOWS); + UserManager.DISALLOW_DEBUGGING_FEATURES, new String[]{MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONTENT_SUGGESTIONS, MANAGE_DEVICE_POLICY_SCREEN_CONTENT); + UserManager.DISALLOW_FACTORY_RESET, new String[]{MANAGE_DEVICE_POLICY_FACTORY_RESET}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONTENT_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT); + UserManager.DISALLOW_FUN, new String[]{MANAGE_DEVICE_POLICY_FUN}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_WIFI, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_INSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_VPN, MANAGE_DEVICE_POLICY_VPN); + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_TETHERING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, MANAGE_DEVICE_POLICY_DISPLAY); + UserManager.DISALLOW_MICROPHONE_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_PRIVATE_DNS, MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS); + UserManager.DISALLOW_MODIFY_ACCOUNTS, new String[]{MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, new String[]{MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_LOCATION, MANAGE_DEVICE_POLICY_LOCATION); + UserManager.DISALLOW_NETWORK_RESET, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_LOCALE, MANAGE_DEVICE_POLICY_LOCALE); + UserManager.DISALLOW_OUTGOING_BEAM, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_DATE_TIME, MANAGE_DEVICE_POLICY_TIME); + UserManager.DISALLOW_OUTGOING_CALLS, new String[]{MANAGE_DEVICE_POLICY_CALLS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_CREDENTIALS, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS); + UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_BRIGHTNESS, MANAGE_DEVICE_POLICY_DISPLAY); + UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH); + UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CHANGE_WIFI_STATE, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CAMERA_TOGGLE, MANAGE_DEVICE_POLICY_CAMERA); + UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CAMERA, MANAGE_DEVICE_POLICY_CAMERA); + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_BLUETOOTH_SHARING, MANAGE_DEVICE_POLICY_BLUETOOTH); + UserManager.DISALLOW_SHARE_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH); + UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_BIOMETRIC, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS); + UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_AUTOFILL, MANAGE_DEVICE_POLICY_AUTOFILL); + UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_APPS_CONTROL, MANAGE_DEVICE_POLICY_APPS_CONTROL); + UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_AMBIENT_DISPLAY, MANAGE_DEVICE_POLICY_DISPLAY); + UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_AIRPLANE_MODE); + UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADJUST_VOLUME, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT); + UserManager.DISALLOW_UNMUTE_DEVICE, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADD_WIFI_CONFIG, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_UNMUTE_MICROPHONE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADD_USER, MANAGE_DEVICE_POLICY_USERS); + UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADD_CLONE_PROFILE, MANAGE_DEVICE_POLICY_PROFILES); + UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, MANAGE_DEVICE_POLICY_PROFILES); + UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CELLULAR_2G, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_WIFI_TETHERING, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, - MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION); + UserManager.ENSURE_VERIFY_APPS, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}); // Restrictions not allowed to be set by admins. USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_RECORD_AUDIO, null); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_WALLPAPER, null); - USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_DEFAULT_APPS, null); } private EnforcingAdmin enforcePermissionForUserRestriction(ComponentName who, String userRestriction, String callerPackageName, int userId) { - String permission = USER_RESTRICTION_PERMISSIONS.get(userRestriction); - if (permission != null) { - return enforcePermissionAndGetEnforcingAdmin(who, permission, callerPackageName, - userId); - } + String[] permissions = USER_RESTRICTION_PERMISSIONS.get(userRestriction); + if (permissions.length > 0) { + try { + return enforcePermissionsAndGetEnforcingAdmin(who, permissions, callerPackageName, + userId); + } catch (SecurityException e) { + throw new SecurityException("Caller does not hold the required permission for this " + + "user restriction: " + userRestriction + ".\n" + e.getMessage()); + } + } throw new SecurityException("Admins are not permitted to set User Restriction: " + userRestriction); } @@ -14017,9 +14047,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); if (isPolicyEngineForFinanceFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin( who, - MANAGE_DEVICE_POLICY_APPS_CONTROL, + new String[]{ + MANAGE_DEVICE_POLICY_APPS_CONTROL, + MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL + }, caller.getPackageName(), caller.getUserId()); mDevicePolicyEngine.setLocalPolicy( @@ -16626,10 +16659,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA); SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO); SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); - SENSOR_PERMISSIONS.add( - Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE); - SENSOR_PERMISSIONS.add( - Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND); } private boolean canGrantPermission(CallerIdentity caller, String permission, @@ -22422,6 +22451,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + // Permission that will need to be created in V. + private static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = + "manage_device_policy_block_uninstall"; + private static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = + "manage_device_policy_camera_toggle"; + private static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = + "manage_device_policy_microphone_toggle"; + // DPC types private static final int DEFAULT_DEVICE_OWNER = 0; private static final int FINANCED_DEVICE_OWNER = 1; @@ -22645,7 +22682,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { { DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS); - DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, DELEGATION_BLOCK_UNINSTALL); + DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS); } @@ -22724,6 +22761,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA_TOGGLE, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, @@ -22740,6 +22781,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES, @@ -22770,13 +22813,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. Only one permission provided in the list needs to be granted to pass this + * check. + * The given permissions will be checked along with their associated cross-user permissions if + * they exists and the target user is different to the calling user. + * Returns an {@link EnforcingAdmin} for the caller. + * + * @param admin the component name of the admin. + * @param callerPackageName The package name of the calling application. + * @param permissions an array of permission names to be checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private EnforcingAdmin enforcePermissionsAndGetEnforcingAdmin(@Nullable ComponentName admin, + String[] permissions, String callerPackageName, int targetUserId) { + enforcePermissions(permissions, callerPackageName, targetUserId); + return getEnforcingAdminForCaller(admin, callerPackageName); + } + + /** + * Checks if the calling process has been granted permission to apply a device policy on a * specific user. * The given permission will be checked along with its associated cross-user permission if it * exists and the target user is different to the calling user. * Returns an {@link EnforcingAdmin} for the caller. * * @param admin the component name of the admin. - * @param callerPackageName The package name of the calling application. + * @param callerPackageName The package name of the calling application. * @param permission The name of the permission being checked. * @param targetUserId The userId of the user which the caller needs permission to act on. * @throws SecurityException if the caller has not been granted the given permission, @@ -22834,6 +22898,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { new HashMap<>(); /** + * Checks if the calling process has been granted permission to apply a device policy. + * + * @param callerPackageName The package name of the calling application. + * @param permission The name of the permission being checked. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private void enforcePermission(String permission, String callerPackageName) + throws SecurityException { + if (!hasPermission(permission, callerPackageName)) { + throw new SecurityException("Caller does not have the required permissions for " + + "this user. Permission required: " + + permission + + "."); + } + } + + + /** * Checks if the calling process has been granted permission to apply a device policy on a * specific user. * The given permission will be checked along with its associated cross-user permission if it @@ -22847,17 +22930,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { */ private void enforcePermission(String permission, String callerPackageName, int targetUserId) throws SecurityException { - if (!hasPermission(permission, callerPackageName, targetUserId)) { - // TODO(b/276920002): Split the error messages so that the cross-user permission - // is only mentioned when it is needed. + enforcePermission(permission, callerPackageName); + if (targetUserId != getCallerIdentity(callerPackageName).getUserId()) { + enforcePermission(CROSS_USER_PERMISSIONS.get(permission), callerPackageName); + } + } + + /** + * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. Only one of the given permissions will be required to be held to pass this + * check. + * The given permissions will be checked along with their associated cross-user permissions if + * they exist and the target user is different to the calling user. + * + * @param permissions An array of the names of the permissions being checked. + * @param callerPackageName The package name of the calling application. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private void enforcePermissions(String[] permissions, String callerPackageName, + int targetUserId) throws SecurityException { + String heldPermission = ""; + for (String permission : permissions) { + if (hasPermission(permission, callerPackageName)) { + heldPermission = permission; + break; + } + } + if (heldPermission.isEmpty()) { throw new SecurityException("Caller does not have the required permissions for " - + "this user. Permissions required: {" - + permission - + ", " - + CROSS_USER_PERMISSIONS.get(permission) - + "(if calling cross-user)" - + "}"); + + "this user. One of the following permission required: " + + Arrays.toString(permissions)); } + enforcePermission(heldPermission, callerPackageName, targetUserId); } /** @@ -22867,25 +22973,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * exists and the target user is different to the calling user. * * @param callerPackageName The package name of the calling application. + * @param adminPolicy The admin policy that should grant holders permission. * @param permission The name of the permission being checked. * @param targetUserId The userId of the user which the caller needs permission to act on. * @throws SecurityException if the caller has not been granted the given permission, * the associated cross-user permission if the caller's user is different to the target user. */ private void enforcePermission(String permission, int adminPolicy, - String callerPackageName, int targetUserId) - throws SecurityException { - if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) { - // TODO(b/276920002): Split the error messages so that the cross-user permission - // is only mentioned when it is needed. - throw new SecurityException("Caller does not have the required permissions for " - + "this user. Permissions required: {" - + permission - + ", " - + CROSS_USER_PERMISSIONS.get(permission) - + "(if calling cross-user)" - + "}"); + String callerPackageName, int targetUserId) throws SecurityException { + if (hasAdminPolicy(adminPolicy, callerPackageName)) { + return; } + enforcePermission(permission, callerPackageName, targetUserId); } /** @@ -22909,6 +23008,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforcePermission(permission, callerPackageName, targetUserId); } + private boolean hasAdminPolicy(int adminPolicy, String callerPackageName) { + CallerIdentity caller = getCallerIdentity(callerPackageName); + ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller); + return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy); + } + /** * Return whether the calling process has been granted permission to apply a device policy on * a specific user. @@ -22921,24 +23026,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { CallerIdentity caller = getCallerIdentity(callerPackageName); boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName()); boolean hasPermissionOnTargetUser = true; - if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) { - hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission), - caller.getPackageName()); + if (hasPermissionOnOwnUser && caller.getUserId() != targetUserId) { + hasPermissionOnTargetUser = hasPermissionOnTargetUser + && hasPermission(CROSS_USER_PERMISSIONS.get(permission), + caller.getPackageName()); } return hasPermissionOnOwnUser && hasPermissionOnTargetUser; } - private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName, - int adminPolicy, int targetUserId) { - CallerIdentity caller = getCallerIdentity(callerPackageName); - if (hasPermission(permission, caller.getPackageName(), targetUserId)) { - return true; - } - ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller); - return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy); - } - /** * Return whether the calling process has been granted the given permission. * diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 509a66b4f8f7..8c2468af1146 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -284,6 +284,7 @@ final class PolicyDefinition<V> { private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>(); private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>(); + // TODO(b/277218360): Revisit policies that should be marked as global-only. static { POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY, @@ -312,8 +313,9 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put( UserManager.DISALLOW_WIFI_TETHERING, POLICY_FLAG_GLOBAL_ONLY_POLICY); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_GRANT_ADMIN, /* flags= */ 0); + // TODO: set as global only once we get rid of the mapping USER_RESTRICTION_FLAGS.put( - UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, POLICY_FLAG_GLOBAL_ONLY_POLICY); + UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, /* flags= */ 0); USER_RESTRICTION_FLAGS.put( UserManager.DISALLOW_WIFI_DIRECT, POLICY_FLAG_GLOBAL_ONLY_POLICY); USER_RESTRICTION_FLAGS.put( @@ -333,8 +335,10 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH_SHARING, /* flags= */ 0); + // This effectively always applies globally, but it can be set on the profile + // parent, check the javadocs on the restriction for more info. USER_RESTRICTION_FLAGS.put( - UserManager.DISALLOW_USB_FILE_TRANSFER, POLICY_FLAG_GLOBAL_ONLY_POLICY); + UserManager.DISALLOW_USB_FILE_TRANSFER, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CREDENTIALS, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_USER, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, /* flags= */ 0); @@ -344,8 +348,10 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_DATE_TIME, /* flags= */ 0); USER_RESTRICTION_FLAGS.put( UserManager.DISALLOW_CONFIG_TETHERING, /* flags= */ 0); + // This effectively always applies globally, but it can be set on the profile + // parent, check the javadocs on the restriction for more info. USER_RESTRICTION_FLAGS.put( - UserManager.DISALLOW_NETWORK_RESET, POLICY_FLAG_GLOBAL_ONLY_POLICY); + UserManager.DISALLOW_NETWORK_RESET, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_FACTORY_RESET, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_USER, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_MANAGED_PROFILE, /* flags= */ 0); @@ -376,8 +382,7 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNMUTE_DEVICE, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_DATA_ROAMING, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SET_USER_ICON, /* flags= */ 0); - // TODO: double check flags - USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, POLICY_FLAG_GLOBAL_ONLY_POLICY); + USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNIFIED_PASSWORD, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_AUTOFILL, /* flags= */ 0); @@ -390,6 +395,7 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put( UserManager.DISALLOW_CONFIG_PRIVATE_DNS, POLICY_FLAG_GLOBAL_ONLY_POLICY); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MICROPHONE_TOGGLE, /* flags= */ 0); + // TODO: According the UserRestrictionsUtils, this is global only, need to confirm. USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CAMERA_TOGGLE, /* flags= */ 0); // TODO: check if its global only USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BIOMETRIC, /* flags= */ 0); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 492d477fe23a..b1d613109e09 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -125,6 +125,7 @@ import com.android.server.compat.PlatformCompatNative; import com.android.server.connectivity.PacProxyService; import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.coverage.CoverageService; +import com.android.server.cpu.CpuMonitorService; import com.android.server.devicepolicy.DevicePolicyManagerService; import com.android.server.devicestate.DeviceStateManagerService; import com.android.server.display.DisplayManagerService; @@ -1405,6 +1406,15 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(RemoteProvisioningService.class); t.traceEnd(); + // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable + // builds once the Android JobScheduler starts using this service. + if (Build.IS_DEBUGGABLE || Build.IS_ENG) { + // Service for CPU monitor. + t.traceBegin("CpuMonitorService"); + mSystemServiceManager.startService(CpuMonitorService.class); + t.traceEnd(); + } + t.traceEnd(); // startCoreServices } diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 1a7517098d18..7b771aff0055 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -567,36 +567,36 @@ public class RescuePartyTest { // Ensure that no action is taken for cases where the failure reason is unknown assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1), - PackageHealthObserverImpact.USER_IMPACT_NONE); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); // Ensure the correct user impact is returned for each mitigation count. assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2), - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3), - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4), - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); } @Test public void testBootLoopLevels() { RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); - assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_NONE); - assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LOW); - assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LOW); - assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_HIGH); - assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_HIGH); - assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH); + assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); + assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); + assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); + assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index 8211d6fc03a2..ec177c9ac33d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -463,7 +463,8 @@ public final class BroadcastQueueModernImplTest { assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); // Bumping past barrier makes us now runnable - airplaneRecord.terminalCount++; + airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED, + "testRunnableAt_Ordered"); queue.invalidateRunnableAt(); assertTrue(queue.isRunnable()); assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index b6bc02a41c21..90e6a2d0f34a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO; @@ -39,7 +41,6 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -463,7 +464,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleReceiver() for " - + Arrays.toString(invocation.getArguments())); + + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName); assertHealth(); final Intent intent = invocation.getArgument(0); final Bundle extras = invocation.getArgument(5); @@ -485,7 +486,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for " - + Arrays.toString(invocation.getArguments())); + + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName); assertHealth(); final Intent intent = invocation.getArgument(1); final Bundle extras = invocation.getArgument(4); @@ -961,7 +962,7 @@ public class BroadcastQueueTest { } else { // Confirm that app was thawed verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily( - eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER)); + eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER)); // Confirm that we added package to process verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName), @@ -1404,7 +1405,7 @@ public class BroadcastQueueTest { // Finally, verify that we thawed the final receiver verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp), - eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER)); + eq(OOM_ADJ_REASON_FINISH_RECEIVER)); } /** @@ -1980,6 +1981,46 @@ public class BroadcastQueueTest { } /** + * Confirm how many times a pathological broadcast pattern results in OOM + * adjusts; watches for performance regressions. + */ + @Test + public void testOomAdjust_TriggerCount() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + + // Send 8 broadcasts, 4 receivers in the first process, + // and 2 alternating in each of the remaining processes + synchronized (mAms) { + for (int i = 0; i < 8; i++) { + final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), + makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), + makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)))); + } + } + waitForIdle(); + + final int expectedTimes; + switch (mImpl) { + // Original stack requested for every single receiver; yikes + case DEFAULT: expectedTimes = 64; break; + // Modern stack requests once each time we promote a process to + // running; we promote "green" twice, and "blue" and "yellow" once + case MODERN: expectedTimes = 4; break; + default: throw new UnsupportedOperationException(); + } + + verify(mAms, times(expectedTimes)) + .updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER)); + } + + /** * Verify that expected events are triggered when a broadcast is finished. */ @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java index 2b6f2174d49b..08952eab071f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java @@ -24,7 +24,12 @@ import static android.content.Intent.ACTION_TIME_CHANGED; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; -import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount; +import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED; +import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED; +import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING; +import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED; +import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT; +import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount; import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive; import static com.android.server.am.BroadcastRecord.calculateUrgent; import static com.android.server.am.BroadcastRecord.isReceiverEquals; @@ -58,7 +63,6 @@ import androidx.test.filters.SmallTest; import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -79,6 +83,7 @@ import java.util.function.BiFunction; @SmallTest @RunWith(MockitoJUnitRunner.class) public class BroadcastRecordTest { + private static final String TAG = "BroadcastRecordTest"; private static final int USER0 = UserHandle.USER_SYSTEM; private static final int USER1 = USER0 + 1; @@ -120,13 +125,13 @@ public class BroadcastRecordTest { assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10)))); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 0)), false)); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), -10)), false)); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 10)), false)); } @@ -142,12 +147,12 @@ public class BroadcastRecordTest { createResolveInfo(PACKAGE3, getAppId(3), 10)))); assertArrayEquals(new int[] {-1,-1,-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 0), createResolveInfo(PACKAGE2, getAppId(2), 0), createResolveInfo(PACKAGE3, getAppId(3), 0)), false)); assertArrayEquals(new int[] {-1,-1,-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 10), createResolveInfo(PACKAGE3, getAppId(3), 10)), false)); @@ -156,26 +161,176 @@ public class BroadcastRecordTest { @Test public void testIsPrioritized_Yes() { assertTrue(isPrioritized(List.of( - createResolveInfo(PACKAGE1, getAppId(1), -10), + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)))); + createResolveInfo(PACKAGE3, getAppId(3), -10)))); assertTrue(isPrioritized(List.of( - createResolveInfo(PACKAGE1, getAppId(1), 0), + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)))); + createResolveInfo(PACKAGE3, getAppId(3), 0)))); assertArrayEquals(new int[] {0,1,2}, - calculateBlockedUntilTerminalCount(List.of( - createResolveInfo(PACKAGE1, getAppId(1), -10), + calculateBlockedUntilBeyondCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)), false)); + createResolveInfo(PACKAGE3, getAppId(3), -10)), false)); assertArrayEquals(new int[] {0,0,2,3,3}, - calculateBlockedUntilTerminalCount(List.of( - createResolveInfo(PACKAGE1, getAppId(1), 0), - createResolveInfo(PACKAGE2, getAppId(2), 0), + calculateBlockedUntilBeyondCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 20), + createResolveInfo(PACKAGE2, getAppId(2), 20), createResolveInfo(PACKAGE3, getAppId(3), 10), - createResolveInfo(PACKAGE3, getAppId(3), 20), - createResolveInfo(PACKAGE3, getAppId(3), 20)), false)); + createResolveInfo(PACKAGE3, getAppId(3), 0), + createResolveInfo(PACKAGE3, getAppId(3), 0)), false)); + } + + @Test + public void testSetDeliveryState_Single() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0))); + assertEquals(DELIVERY_PENDING, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_DEFERRED, TAG); + assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 1, 1); + + // Identical state change has no effect + r.setDeliveryState(0, DELIVERY_DEFERRED, TAG); + assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 1, 1); + + // Moving to terminal state updates counters + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 1, 0, 1); + + // Trying to change terminal state has no effect + r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG); + assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 1, 0, 1); + } + + @Test + public void testSetDeliveryState_Unordered() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0))); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + // Even though we finish a middle item in the tranche, we're not + // "beyond" it because there is still unfinished work before it + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 1, 0, 0); + + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 2, 0, 2); + + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 3, 0, 3); + } + + @Test + public void testSetDeliveryState_Ordered() { + final BroadcastRecord r = createOrderedBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0))); + assertBlocked(r, false, true, true); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, true); + assertTerminalDeferredBeyond(r, 1, 0, 1); + + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 2, 0, 2); + + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 3, 0, 3); + } + + @Test + public void testSetDeliveryState_DeferUntilActive() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(-10), + createResolveInfoWithPriority(-10), + createResolveInfoWithPriority(-10))); + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_PENDING, TAG); + r.setDeliveryState(1, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(2, DELIVERY_PENDING, TAG); + r.setDeliveryState(3, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(4, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(5, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(6, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(7, DELIVERY_PENDING, TAG); + r.setDeliveryState(8, DELIVERY_DEFERRED, TAG); + + // Verify deferred counts ratchet up, but we're not "beyond" the first + // still-pending receiver + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 0, 6, 0); + + // We're still not "beyond" the first still-pending receiver, even when + // we finish a receiver later in the first tranche + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 1, 6, 0); + + // Completing that last item in first tranche means we now unblock the + // second tranche, and since it's entirely deferred, the third traunche + // is unblocked too + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 2, 6, 7); + + // Moving a deferred item in an earlier tranche back to being pending + // doesn't change the fact that we've already moved beyond it + r.setDeliveryState(1, DELIVERY_PENDING, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 2, 5, 7); + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 3, 5, 7); + + // Completing middle pending item is enough to fast-forward to end + r.setDeliveryState(7, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 4, 5, 9); + + // Moving everyone else directly into a finished state updates all the + // terminal counters + r.setDeliveryState(3, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(4, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(5, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(6, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(8, DELIVERY_SKIPPED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 9, 0, 9); } @Test @@ -688,6 +843,10 @@ public class BroadcastRecordTest { : errorMsg.insert(0, "Contains unexpected receiver: ").toString(); } + private static ResolveInfo createResolveInfoWithPriority(int priority) { + return createResolveInfo(PACKAGE1, getAppId(1), priority); + } + private static ResolveInfo createResolveInfo(String packageName, int uid) { return createResolveInfo(packageName, uid, 0); } @@ -738,21 +897,40 @@ public class BroadcastRecordTest { return excludedList; } + private BroadcastRecord createBroadcastRecord(Intent intent, + List<ResolveInfo> receivers) { + return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */, + null /* options */, false); + } + + private BroadcastRecord createOrderedBroadcastRecord(Intent intent, + List<ResolveInfo> receivers) { + return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */, + null /* options */, true); + } + private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent) { return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */, - null /* options */); + null /* options */, false); } private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent, BroadcastOptions options) { return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */, - options); + options, false); } private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, BroadcastOptions options) { + return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver, + options, false); + } + + private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, + Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, + BroadcastOptions options, boolean ordered) { return new BroadcastRecord( mQueue /* queue */, intent, @@ -774,7 +952,7 @@ public class BroadcastRecordTest { 0 /* resultCode */, null /* resultData */, null /* resultExtras */, - false /* serialized */, + ordered /* serialized */, false /* sticky */, false /* initialSticky */, userId, @@ -789,6 +967,20 @@ public class BroadcastRecordTest { private static boolean isPrioritized(List<Object> receivers) { return BroadcastRecord.isPrioritized( - calculateBlockedUntilTerminalCount(receivers, false), false); + calculateBlockedUntilBeyondCount(receivers, false), false); + } + + private static void assertBlocked(BroadcastRecord r, boolean... blocked) { + assertEquals(r.receivers.size(), blocked.length); + for (int i = 0; i < blocked.length; i++) { + assertEquals("blocked " + i, blocked[i], r.isBlocked(i)); + } + } + + private static void assertTerminalDeferredBeyond(BroadcastRecord r, + int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) { + assertEquals("terminal", expectedTerminalCount, r.terminalCount); + assertEquals("deferred", expectedDeferredCount, r.deferredCount); + assertEquals("beyond", expectedBeyondCount, r.beyondCount); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 485ce33dfb7d..cda5456723fb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -38,11 +38,12 @@ import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY; import static com.android.server.am.ProcessList.BACKUP_APP_ADJ; import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ; import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; @@ -254,12 +255,13 @@ public class MockingOomAdjusterTests { * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int). * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int). */ + @SuppressWarnings("GuardedBy") private void updateOomAdj(ProcessRecord... apps) { if (apps.length == 1) { - sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE); } else { setProcessesToLru(apps); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); sService.mProcessList.getLruProcessesLOSP().clear(); } } @@ -658,7 +660,7 @@ public class MockingOomAdjusterTests { ServiceRecord s = bindService(app, system, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT); @@ -1226,7 +1228,7 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); } @@ -1243,7 +1245,7 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); doReturn(false).when(wpc).isHeavyWeightProcess(); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); @@ -1497,7 +1499,7 @@ public class MockingOomAdjusterTests { client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE); assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState()); assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState()); @@ -1919,7 +1921,7 @@ public class MockingOomAdjusterTests { doReturn(client2).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE); assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT); } @@ -2029,7 +2031,7 @@ public class MockingOomAdjusterTests { setServiceMap(s3, MOCKAPP5_UID, cn3); setServiceMap(c2s, MOCKAPP3_UID, cn4); app2UidRecord.setIdle(false); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, SCHED_GROUP_DEFAULT); @@ -2055,7 +2057,7 @@ public class MockingOomAdjusterTests { anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); doNothing().when(sService.mServices) .scheduleServiceTimeoutLocked(any(ProcessRecord.class)); - sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE); assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState()); assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState()); @@ -2427,7 +2429,7 @@ public class MockingOomAdjusterTests { app2.mState.setHasShownUi(false); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services"); assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services"); @@ -2436,7 +2438,7 @@ public class MockingOomAdjusterTests { app.mState.setAdjType(null); app.mState.setSetAdj(UNKNOWN_ADJ); app.mState.setHasShownUi(false); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); @@ -2445,7 +2447,7 @@ public class MockingOomAdjusterTests { app.mState.setAdjType(null); app.mState.setSetAdj(UNKNOWN_ADJ); s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1; - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); @@ -2463,7 +2465,7 @@ public class MockingOomAdjusterTests { s.lastActivity = now; app.mServices.startService(s); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); @@ -2474,7 +2476,7 @@ public class MockingOomAdjusterTests { app.mState.setSetAdj(UNKNOWN_ADJ); app.mState.setHasShownUi(false); s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1; - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); @@ -2482,7 +2484,7 @@ public class MockingOomAdjusterTests { doReturn(userOther).when(sService.mUserController).getCurrentUserId(); sService.mOomAdjuster.handleUserSwitchedLocked(); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java new file mode 100644 index 000000000000..e1fa8f527261 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java @@ -0,0 +1,92 @@ +/* + * 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.location.gnss; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class GnssAntennaInfoProviderTest { + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock IBinder mBinder; + private GnssNative mGnssNative; + + private GnssAntennaInfoProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration))); + mTestProvider = new GnssAntennaInfoProvider(mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testOnHalStarted() { + verify(mGnssNative, times(1)).startAntennaInfoListening(); + } + + @Test + public void testOnHalRestarted() { + mTestProvider.onHalRestarted(); + verify(mGnssNative, times(2)).startAntennaInfoListening(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java index fd9dfe869d52..bf96b1dec4ac 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java @@ -74,7 +74,6 @@ public class GnssMeasurementsProviderTest { private @Mock Context mContext; private @Mock LocationManagerInternal mInternal; private @Mock GnssConfiguration mMockConfiguration; - private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks; private @Mock IGnssMeasurementsListener mListener1; private @Mock IGnssMeasurementsListener mListener2; private @Mock IBinder mBinder1; @@ -98,7 +97,6 @@ public class GnssMeasurementsProviderTest { Injector injector = new TestInjector(mContext); mGnssNative = spy(Objects.requireNonNull( GnssNative.create(injector, mMockConfiguration))); - mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks); mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative); mGnssNative.register(); } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java new file mode 100644 index 000000000000..64aa4b3fa2ff --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java @@ -0,0 +1,93 @@ +/* + * 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.location.gnss; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.IGnssNavigationMessageListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.util.identity.CallerIdentity; +import android.os.IBinder; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.FakeUserInfoHelper; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +public class GnssNavigationMessageProviderTest { + private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID; + private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000, + "mypackage", "attribution", "listener"); + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock IGnssNavigationMessageListener mListener; + private @Mock IBinder mBinder; + + private GnssNative mGnssNative; + + private GnssNavigationMessageProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(mBinder).when(mListener).asBinder(); + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration))); + mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testAddListener() { + // add a request + mTestProvider.addListener(IDENTITY, mListener); + verify(mGnssNative, times(1)).startNavigationMessageCollection(); + + // remove a request + mTestProvider.removeListener(mListener); + verify(mGnssNative, times(1)).stopNavigationMessageCollection(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java new file mode 100644 index 000000000000..49e5e69933f9 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java @@ -0,0 +1,104 @@ +/* + * 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.location.gnss; + + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.IGnssNmeaListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.util.identity.CallerIdentity; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.FakeUserInfoHelper; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class GnssNmeaProviderTest { + + private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID; + private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000, + "mypackage", "attribution", "listener"); + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock IGnssNmeaListener mListener; + private @Mock IBinder mBinder; + + private GnssNative mGnssNative; + + private GnssNmeaProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(mBinder).when(mListener).asBinder(); + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration))); + mTestProvider = new GnssNmeaProvider(injector, mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testAddListener() { + // add a request + mTestProvider.addListener(IDENTITY, mListener); + verify(mGnssNative, times(1)).startNmeaMessageCollection(); + + // remove a request + mTestProvider.removeListener(mListener); + verify(mGnssNative, times(1)).stopNmeaMessageCollection(); + } + +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java new file mode 100644 index 000000000000..ce2aec7f8d5d --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java @@ -0,0 +1,103 @@ +/* + * 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.location.gnss; + + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.IGnssStatusListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.util.identity.CallerIdentity; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.FakeUserInfoHelper; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class GnssStatusProviderTest { + private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID; + private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000, + "mypackage", "attribution", "listener"); + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock IGnssStatusListener mListener; + private @Mock IBinder mBinder; + + private GnssNative mGnssNative; + + private GnssStatusProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(mBinder).when(mListener).asBinder(); + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration))); + mTestProvider = new GnssStatusProvider(injector, mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testAddListener() { + // add a request + mTestProvider.addListener(IDENTITY, mListener); + verify(mGnssNative, times(1)).startSvStatusCollection(); + + // remove a request + mTestProvider.removeListener(mListener); + verify(mGnssNative, times(1)).stopSvStatusCollection(); + } + +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java index b7ab6f80167e..2d962acfe665 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java @@ -562,6 +562,26 @@ public final class FakeGnssHal extends GnssNative.GnssHal { } @Override + protected boolean startSvStatusCollection() { + return true; + } + + @Override + protected boolean stopSvStatusCollection() { + return true; + } + + @Override + public boolean startNmeaMessageCollection() { + return true; + } + + @Override + public boolean stopNmeaMessageCollection() { + return true; + } + + @Override protected int getBatchSize() { return mBatchSize; } diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java index 0be678af12dc..541b07782b29 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java @@ -16,31 +16,65 @@ package com.android.server.rollback; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; + import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; import android.content.pm.VersionedPackage; +import android.content.rollback.PackageRollbackInfo; +import android.content.rollback.RollbackInfo; +import android.content.rollback.RollbackManager; import android.util.Log; import android.util.Xml; import androidx.test.runner.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.PackageWatchdog; import com.android.server.SystemConfig; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; import org.xmlpull.v1.XmlPullParser; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.List; import java.util.Scanner; + @RunWith(AndroidJUnit4.class) public class RollbackPackageHealthObserverTest { + @Mock + private Context mMockContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PackageWatchdog mMockPackageWatchdog; + @Mock + RollbackManager mRollbackManager; + @Mock + RollbackInfo mRollbackInfo; + @Mock + PackageRollbackInfo mPackageRollbackInfo; + + private MockitoSession mSession; + private static final String APP_A = "com.package.a"; + private static final long VERSION_CODE = 1L; private static final String LOG_TAG = "RollbackPackageHealthObserverTest"; private SystemConfig mSysConfig; @@ -50,17 +84,74 @@ public class RollbackPackageHealthObserverTest { @Before public void setup() { mSysConfig = new SystemConfigTestClass(); + + mSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .spyStatic(PackageWatchdog.class) + .startMocking(); + + // Mock PackageWatchdog + doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) + .when(() -> PackageWatchdog.getInstance(mMockContext)); + + } + + @After + public void tearDown() throws Exception { + mSession.finishMocking(); } /** - * Subclass of SystemConfig without running the constructor. - */ + * Subclass of SystemConfig without running the constructor. + */ private class SystemConfigTestClass extends SystemConfig { SystemConfigTestClass() { - super(false); + super(false); } } + @Test + public void testHealthCheckLevels() { + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext)); + VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE); + + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + + // Crashes with no rollbacks available + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1)); + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); + + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo)); + when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo)); + when(mPackageRollbackInfo.getVersionRolledBackFrom()).thenReturn(testFailedPackage); + + // native crash + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, + observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1)); + // non-native crash + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, + observer.onHealthCheckFailed(testFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); + // Second non-native crash again + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + observer.onHealthCheckFailed(testFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 2)); + // Subsequent crashes when rollbacks have completed + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of()); + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onHealthCheckFailed(testFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 3)); + } + /** * Test that isAutomaticRollbackDenied works correctly when packages that are not * denied are sent. @@ -77,7 +168,7 @@ public class RollbackPackageHealthObserverTest { readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.test.package", 1))).isEqualTo(false); + new VersionedPackage("com.test.package", 1))).isEqualTo(false); } /** @@ -96,7 +187,7 @@ public class RollbackPackageHealthObserverTest { readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.android.vending", 1))).isEqualTo(true); + new VersionedPackage("com.android.vending", 1))).isEqualTo(true); } /** @@ -109,7 +200,7 @@ public class RollbackPackageHealthObserverTest { readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.android.vending", 1))).isEqualTo(false); + new VersionedPackage("com.android.vending", 1))).isEqualTo(false); } /** diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index dbf5021d3c6b..26a3ae110525 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -43,6 +43,7 @@ import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; import android.content.Context; +import android.content.res.Resources; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -52,6 +53,7 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Binder; @@ -83,6 +85,7 @@ public class AuthSessionTest { private static final long TEST_REQUEST_ID = 22; @Mock private Context mContext; + @Mock private Resources mResources; @Mock private BiometricContext mBiometricContext; @Mock private ITrustManager mTrustManager; @Mock private DevicePolicyManager mDevicePolicyManager; @@ -104,6 +107,7 @@ public class AuthSessionTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + when(mContext.getResources()).thenReturn(mResources); when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class)); when(mBiometricContext.updateContext(any(), anyBoolean())) .thenAnswer(invocation -> invocation.getArgument(0)); @@ -342,6 +346,33 @@ public class AuthSessionTest { testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null)); } + @Test + public void testCallbackOnAcquired() throws RemoteException { + final String acquiredStr = "test_acquired_info_callback"; + final String acquiredStrVendor = "test_acquired_info_callback_vendor"; + setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR); + + final AuthSession session = createAuthSession(mSensors, + false /* checkDevicePolicyManager */, + Authenticators.BIOMETRIC_STRONG, + TEST_REQUEST_ID, + 0 /* operationId */, + 0 /* userId */); + + when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial)) + .thenReturn(acquiredStr); + session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0); + verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr)); + verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr)); + + when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor)) + .thenReturn(new String[]{acquiredStrVendor}); + session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0); + verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor)); + verify(mClientReceiver).onAcquired( + eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor)); + } + // TODO (b/208484275) : Enable these tests // @Test // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java index 550204b99323..4cfbb9520d5f 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java @@ -105,6 +105,7 @@ public class LocaleManagerServiceTest { mMockPackageManager = mock(PackageManager.class); mMockPackageMonitor = mock(PackageMonitor.class); + doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt()); // For unit tests, set the default installer info doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager) .getInstallSourceInfo(anyString()); diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java index da9de2562930..e20f1e7065d4 100644 --- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java @@ -131,6 +131,7 @@ public class SystemAppUpdateTrackerTest { doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); doReturn(InstrumentationRegistry.getContext().getContentResolver()) .when(mMockContext).getContentResolver(); + doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt()); mStoragefile = new AtomicFile(new File( Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml")); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index ff6c9769b69f..516fb4aa40c6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -15,23 +15,31 @@ */ package com.android.server.notification; -import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY; +import static android.app.Notification.FLAG_AUTO_CANCEL; +import static android.app.Notification.FLAG_BUBBLE; +import static android.app.Notification.FLAG_CAN_COLORIZE; +import static android.app.Notification.FLAG_FOREGROUND_SERVICE; +import static android.app.Notification.FLAG_NO_CLEAR; +import static android.app.Notification.FLAG_ONGOING_EVENT; + +import static com.android.server.notification.GroupHelper.BASE_FLAGS; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import android.annotation.SuppressLint; import android.app.Notification; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; +import android.util.ArrayMap; import androidx.test.runner.AndroidJUnit4; @@ -45,11 +53,10 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; @SmallTest +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class. @RunWith(AndroidJUnit4.class) public class GroupHelperTest extends UiServiceTestCase { private @Mock GroupHelper.Callback mCallback; @@ -82,21 +89,104 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testNoGroup_postingUnderLimit() throws Exception { + public void testGetAutogroupSummaryFlags_noChildren() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + + assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_oneOngoing() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_ONGOING_EVENT); + children.put("c", FLAG_BUBBLE); + + assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_oneOngoingNoClear() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR); + children.put("c", FLAG_BUBBLE); + + assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_oneOngoingBubble() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE); + children.put("c", FLAG_BUBBLE); + + assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_multipleOngoing() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_ONGOING_EVENT); + children.put("c", FLAG_BUBBLE); + children.put("d", FLAG_ONGOING_EVENT); + + assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_oneAutoCancel() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_AUTO_CANCEL); + children.put("c", FLAG_BUBBLE); + + assertEquals(BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_allAutoCancel() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", FLAG_AUTO_CANCEL); + children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE); + children.put("c", FLAG_AUTO_CANCEL); + children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE); + + assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", FLAG_AUTO_CANCEL); + children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE); + children.put("c", FLAG_AUTO_CANCEL); + children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT); + + assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testNoGroup_postingUnderLimit() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false); } - verify(mCallback, never()).addAutoGroupSummary( - eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean()); - verify(mCallback, never()).addAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verifyZeroInteractions(mCallback); } @Test - public void testNoGroup_multiPackage() throws Exception { + public void testNoGroup_multiPackage() { final String pkg = "package"; final String pkg2 = "package2"; for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { @@ -105,31 +195,23 @@ public class GroupHelperTest extends UiServiceTestCase { } mGroupHelper.onNotificationPosted( getSbn(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false); - verify(mCallback, never()).addAutoGroupSummary( - eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean()); - verify(mCallback, never()).addAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verifyZeroInteractions(mCallback); } @Test - public void testNoGroup_multiUser() throws Exception { + public void testNoGroup_multiUser() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false); } mGroupHelper.onNotificationPosted( - getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.ALL), false); - verify(mCallback, never()).addAutoGroupSummary( - anyInt(), eq(pkg), anyString(), anyBoolean()); - verify(mCallback, never()).addAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false); + verifyZeroInteractions(mCallback); } @Test - public void testNoGroup_someAreGrouped() throws Exception { + public void testNoGroup_someAreGrouped() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { mGroupHelper.onNotificationPosted( @@ -137,233 +219,344 @@ public class GroupHelperTest extends UiServiceTestCase { } mGroupHelper.onNotificationPosted( getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"), false); - verify(mCallback, never()).addAutoGroupSummary( - eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean()); - verify(mCallback, never()).addAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verifyZeroInteractions(mCallback); } @Test - public void testPostingOverLimit() throws Exception { + public void testAddSummary() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { mGroupHelper.onNotificationPosted( getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false); } - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false)); + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); } @Test - public void testPostingOverLimit_addsOngoingFlag() throws Exception { + public void testAddSummary_oneChildOngoing_summaryOngoing() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); if (i == 0) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; } mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(true)); + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(BASE_FLAGS | FLAG_ONGOING_EVENT)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); } @Test - public void testAutoGroupCount_addingNoGroupSBN() { + public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() { final String pkg = "package"; - ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + } + mGroupHelper.onNotificationPosted(sbn, false); } + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); + } - for (StatusBarNotification sbn: notifications) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + @Test + public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + mGroupHelper.onNotificationPosted(sbn, false); } + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(BASE_FLAGS | FLAG_AUTO_CANCEL)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); + } - for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + @Test + public void testAddSummary_summaryAutoCancelNoClear() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + if (i == 0) { + sbn.getNotification().flags |= FLAG_NO_CLEAR; + } + mGroupHelper.onNotificationPosted(sbn, false); } - - verify(mCallback, times(AUTOGROUP_AT_COUNT + 1)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); - - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), AUTOGROUP_AT_COUNT + 1); + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); } @Test - public void testAutoGroupCount_UpdateNotification() { + public void testAutoGrouped_allOngoing_updateChildNotOngoing() { final String pkg = "package"; - ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); - } - for (StatusBarNotification sbn: notifications) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + mGroupHelper.onNotificationPosted(sbn, false); } - notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT; - mGroupHelper.onNotificationUpdated(notifications.get(0)); + // One notification is no longer ongoing + notifications.get(0).getNotification().flags &= ~FLAG_ONGOING_EVENT; + mGroupHelper.onNotificationPosted(notifications.get(0), true); - verify(mCallback, times(AUTOGROUP_AT_COUNT + 2)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); - - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), AUTOGROUP_AT_COUNT); + // Summary should keep FLAG_ONGOING_EVENT if any child has it + verify(mCallback).updateAutogroupSummary( + anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT)); } @Test - public void testAutoGroupCount_UpdateNotificationAfterChanges() { + public void testAutoGrouped_singleOngoing_removeOngoingChild() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + } + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + mGroupHelper.onNotificationPosted(sbn, false); } - for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); - } + // remove ongoing + mGroupHelper.onNotificationRemoved(notifications.get(0)); - notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT; + // Summary is no longer ongoing + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS)); + } - mGroupHelper.onNotificationUpdated(notifications.get(0)); + @Test + public void testAutoGrouped_noOngoing_updateOngoingChild() { + final String pkg = "package"; - notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT; + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + notifications.add(sbn); + } - mGroupHelper.onNotificationUpdated(notifications.get(0)); + for (StatusBarNotification sbn: notifications) { + mGroupHelper.onNotificationPosted(sbn, false); + } - verify(mCallback, times(AUTOGROUP_AT_COUNT + 3)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + // update to ongoing + notifications.get(0).getNotification().flags |= FLAG_ONGOING_EVENT; + mGroupHelper.onNotificationPosted(notifications.get(0), true); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), AUTOGROUP_AT_COUNT + 1); + // Summary is now ongoing + verify(mCallback).updateAutogroupSummary( + anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT)); } @Test - public void testAutoGroupCount_RemoveNotification() { + public void testAutoGrouped_noOngoing_addOngoingChild() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + mGroupHelper.onNotificationPosted(sbn, false); } - for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + // add ongoing + StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT + 1, null, UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + mGroupHelper.onNotificationPosted(sbn, true); + + // Summary is now ongoing + verify(mCallback).updateAutogroupSummary( + anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT)); + } + + @Test + public void testAutoGrouped_singleOngoing_appGroupOngoingChild() { + final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + } + notifications.add(sbn); } - mGroupHelper.onNotificationRemoved(notifications.get(0)); + for (StatusBarNotification sbn: notifications) { + mGroupHelper.onNotificationPosted(sbn, false); + } - verify(mCallback, times(AUTOGROUP_AT_COUNT + 2)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + // app group the ongoing child + StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now"); + mGroupHelper.onNotificationPosted(sbn, true); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), AUTOGROUP_AT_COUNT); + // Summary is no longer ongoing + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS)); } - @Test - public void testAutoGroupCount_UpdateToNoneOngoingNotification() { + public void testAutoGrouped_singleOngoing_removeNonOngoingChild() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + } + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + mGroupHelper.onNotificationPosted(sbn, false); } - for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + // remove ongoing + mGroupHelper.onNotificationRemoved(notifications.get(1)); + + // Summary is still ongoing + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); + } + + @Test + public void testAutoGrouped_allAutoCancel_updateChildNotAutoCancel() { + final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + notifications.add(sbn); } - notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - mGroupHelper.onNotificationUpdated(notifications.get(0)); + for (StatusBarNotification sbn: notifications) { + mGroupHelper.onNotificationPosted(sbn, false); + } - verify(mCallback, times(1)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + // One notification is no longer autocancelable + notifications.get(0).getNotification().flags &= ~FLAG_AUTO_CANCEL; + mGroupHelper.onNotificationPosted(notifications.get(0), true); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), 1); + // Summary should no longer be autocancelable + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS)); } @Test - public void testAutoGroupCount_AddOneOngoingNotification() { + public void testAutoGrouped_almostAllAutoCancel_updateChildAutoCancel() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i != 0) { + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + } + notifications.add(sbn); } - StatusBarNotification sbn = notifications.get(AUTOGROUP_AT_COUNT); - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); - - for (StatusBarNotification current: notifications) { - mGroupHelper.onNotificationPosted(current, true); + for (StatusBarNotification sbn: notifications) { + mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(1)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + // Missing notification is now autocancelable + notifications.get(0).getNotification().flags |= FLAG_AUTO_CANCEL; + mGroupHelper.onNotificationPosted(notifications.get(0), true); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), 1); + // Summary should now autocancelable + verify(mCallback).updateAutogroupSummary( + anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL)); } @Test - public void testAutoGroupCount_UpdateNoneOngoing() { + public void testAutoGrouped_allAutoCancel_updateChildAppGrouped() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + mGroupHelper.onNotificationPosted(sbn, false); + } + + // One notification is now grouped by app + StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now"); + mGroupHelper.onNotificationPosted(sbn, true); + + // Summary should be still be autocancelable + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); + } + + @Test + public void testAutoGrouped_allAutoCancel_removeChild() { + final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(0)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + mGroupHelper.onNotificationRemoved(notifications.get(0)); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg), 0); + // Summary should still be autocancelable + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); } - @Test - public void testDropToZeroRemoveGroup() throws Exception { + public void testDropToZeroRemoveGroup() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -371,7 +564,8 @@ public class GroupHelperTest extends UiServiceTestCase { posted.add(sbn); mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false)); + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -390,7 +584,7 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testAppStartsGrouping() throws Exception { + public void testAppStartsGrouping() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -399,7 +593,7 @@ public class GroupHelperTest extends UiServiceTestCase { mGroupHelper.onNotificationPosted(sbn, false); } verify(mCallback, times(1)).addAutoGroupSummary( - anyInt(), eq(pkg), anyString(), anyBoolean()); + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -408,9 +602,10 @@ public class GroupHelperTest extends UiServiceTestCase { for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group"); - mGroupHelper.onNotificationPosted(sbn, false); + sbn.setOverrideGroupKey("autogrouped"); + mGroupHelper.onNotificationPosted(sbn, true); verify(mCallback, times(1)).removeAutoGroup(sbn.getKey()); - if (i < AUTOGROUP_AT_COUNT -1) { + if (i < AUTOGROUP_AT_COUNT - 1) { verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); } } @@ -418,8 +613,7 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() - throws Exception { + public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -427,7 +621,8 @@ public class GroupHelperTest extends UiServiceTestCase { posted.add(sbn); mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false)); + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -441,10 +636,7 @@ public class GroupHelperTest extends UiServiceTestCase { Mockito.reset(mCallback); // only one child remains - Map<String, LinkedHashSet<String>> ungroupedForUser = - mGroupHelper.mUngroupedNotifications.get(UserHandle.USER_SYSTEM); - assertNotNull(ungroupedForUser); - assertEquals(1, ungroupedForUser.get(pkg).size()); + assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg)); // Add new notification; it should be autogrouped even though the total count is // < AUTOGROUP_AT_COUNT @@ -454,5 +646,8 @@ public class GroupHelperTest extends UiServiceTestCase { verify(mCallback, times(1)).addAutoGroup(sbn.getKey()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS)); + verify(mCallback, never()).addAutoGroupSummary( + anyInt(), anyString(), anyString(), anyInt()); } } 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 eceb589bba76..9cfdaa7cad0c 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -754,13 +754,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id, String groupKey, boolean isSummary) { + return generateNotificationRecord(channel, id, "tag" + System.currentTimeMillis(), groupKey, + isSummary); + } + + private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id, + String tag, String groupKey, boolean isSummary) { Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon) .setGroup(groupKey) .setGroupSummary(isSummary); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, - "tag" + System.currentTimeMillis(), mUid, 0, + tag, mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); } @@ -1899,7 +1905,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mSummaryByGroupKey.put("pkg", summary); mService.mAutobundledSummaries.put(0, new ArrayMap<>()); mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey()); - mService.updateAutobundledSummaryFlags(0, "pkg", true, false); + mService.updateAutobundledSummaryFlags( + 0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false); assertTrue(summary.getSbn().isOngoing()); } @@ -1915,7 +1922,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey()); mService.mSummaryByGroupKey.put("pkg", summary); - mService.updateAutobundledSummaryFlags(0, "pkg", false, false); + mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false); assertFalse(summary.getSbn().isOngoing()); } @@ -2897,7 +2904,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true); NotificationRecord r = mService.createAutoGroupSummary( - temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false); + temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0); assertThat(r.isImportanceFixed()).isTrue(); } @@ -4213,7 +4220,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception { + public void testOnlyAutogroupIfNeeded_newNotification_ghUpdate() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = @@ -4226,17 +4233,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnlyAutogroupIfGroupChanged_groupChanged_autogroups() - throws Exception { - NotificationRecord r = - generateNotificationRecord(mTestNotificationChannel, 0, "group", false); + public void testOnlyAutogroupIfNeeded_groupChanged_ghUpdate() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", "group", false); mService.addNotification(r); - r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); - mService.addEnqueuedNotification(r); + NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", null, false); + mService.addEnqueuedNotification(update); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(update.getKey(), + update.getSbn().getPackageName(), update.getUid(), + SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -4244,16 +4252,39 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnlyAutogroupIfGroupChanged_noGroupChanged_autogroups() - throws Exception { - NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group", - false); + public void testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", "group", false); mService.addNotification(r); - mService.addEnqueuedNotification(r); + NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", null, false); + update.getNotification().flags = FLAG_AUTO_CANCEL; + mService.addEnqueuedNotification(update); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(update.getKey(), + update.getSbn().getPackageName(), update.getUid(), + SystemClock.elapsedRealtime()); + runnable.run(); + waitForIdle(); + + verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean()); + } + + @Test + public void testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false); + mService.addNotification(r); + NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false); + update.getNotification().color = Color.BLACK; + mService.addEnqueuedNotification(update); + + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(update.getKey(), + update.getSbn().getPackageName(), + update.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -10220,10 +10251,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // grouphelper is a mock here, so make the calls it would make - // add summary; wait for it to be posted - mService.addAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(), nr1.getKey(), - true); - waitForIdle(); + // add summary + mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(), + nr1.getSbn().getPackageName(), nr1.getKey(), + GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT)); // cancel both children mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(), @@ -10232,9 +10263,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nr1.getSbn().getId(), nr1.getSbn().getUserId()); waitForIdle(); - // group helper would send 'remove flag' and then 'remove summary' events - mService.updateAutobundledSummaryFlags(nr1.getUserId(), nr1.getSbn().getPackageName(), - false, false); + // group helper would send 'remove summary' event mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName()); waitForIdle(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java index e6569f7e0ce2..9fe0e49c4ab8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java @@ -98,6 +98,41 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { } @Test + public void testHasDiffs_autoBundled() { + NotificationRecord r = generateRecord(); + + NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData( + 1, + r.getPackageVisibilityOverride(), + r.canShowBadge(), + r.canBubble(), + r.getNotification().isBubbleNotification(), + r.getChannel(), + r.getGroupKey(), + r.getPeopleOverride(), + r.getSnoozeCriteria(), + r.getUserSentiment(), + r.getSuppressedVisualEffects(), + r.getSystemGeneratedSmartActions(), + r.getSmartReplies(), + r.getImportance(), + r.getRankingScore(), + r.isConversation(), + r.getProposedImportance(), + r.hasSensitiveContent()); + + Bundle signals = new Bundle(); + signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group"); + Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0); + r.addAdjustment(adjustment); + NotificationAdjustmentExtractor adjustmentExtractor = new NotificationAdjustmentExtractor(); + adjustmentExtractor.process(r); + + assertTrue(extractorData.hasDiffForRankingLocked(r, 1)); + assertTrue(extractorData.hasDiffForLoggingLocked(r, 1)); + } + + @Test public void testHasDiffs_sensitiveContentChange() { NotificationRecord r = generateRecord(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java new file mode 100644 index 000000000000..bcd807ab6d2f --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -0,0 +1,319 @@ +/* + * 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.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import android.content.ComponentName; +import android.net.Uri; +import android.provider.Settings; +import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeDiff; +import android.service.notification.ZenPolicy; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.ArrayMap; + +import androidx.test.filters.SmallTest; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ZenModeDiffTest extends UiServiceTestCase { + // version is not included in the diff; manual & automatic rules have special handling + public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS = + Set.of("version", "manualRule", "automaticRules"); + + @Test + public void testRuleDiff_addRemoveSame() { + // Test add, remove, and both sides same + ZenModeConfig.ZenRule r = makeRule(); + + // Both sides same rule + ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r); + assertFalse(dSame.hasDiff()); + + // from existent rule to null: expect deleted + ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null); + assertTrue(deleted.hasDiff()); + assertTrue(deleted.wasRemoved()); + + // from null to new rule: expect added + ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r); + assertTrue(added.hasDiff()); + assertTrue(added.wasAdded()); + } + + @Test + public void testRuleDiff_fieldDiffs() throws Exception { + // Start these the same + ZenModeConfig.ZenRule r1 = makeRule(); + ZenModeConfig.ZenRule r2 = makeRule(); + + // maps mapping field name -> expected output value as we set diffs + ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); + ArrayMap<String, Object> expectedTo = new ArrayMap<>(); + List<Field> fieldsForDiff = getFieldsForDiffCheck( + ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule + generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo); + + ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2); + assertTrue(d.hasDiff()); + + // Now diff them and check that each of the fields has a diff + for (Field f : fieldsForDiff) { + String name = f.getName(); + assertNotNull("diff not found for field: " + name, d.getDiffForField(name)); + assertTrue(d.getDiffForField(name).hasDiff()); + assertTrue("unexpected field: " + name, expectedFrom.containsKey(name)); + assertTrue("unexpected field: " + name, expectedTo.containsKey(name)); + assertEquals(expectedFrom.get(name), d.getDiffForField(name).from()); + assertEquals(expectedTo.get(name), d.getDiffForField(name).to()); + } + } + + @Test + public void testConfigDiff_addRemoveSame() { + // Default config, will test add, remove, and no change + ZenModeConfig c = new ZenModeConfig(); + + ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c); + assertFalse(dSame.hasDiff()); + + ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c); + assertTrue(added.hasDiff()); + assertTrue(added.wasAdded()); + + ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null); + assertTrue(removed.hasDiff()); + assertTrue(removed.wasRemoved()); + } + + @Test + public void testConfigDiff_fieldDiffs() throws Exception { + // these two start the same + ZenModeConfig c1 = new ZenModeConfig(); + ZenModeConfig c2 = new ZenModeConfig(); + + // maps mapping field name -> expected output value as we set diffs + ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); + ArrayMap<String, Object> expectedTo = new ArrayMap<>(); + List<Field> fieldsForDiff = getFieldsForDiffCheck( + ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS); + generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo); + + ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(d.hasDiff()); + + // Now diff them and check that each of the fields has a diff + for (Field f : fieldsForDiff) { + String name = f.getName(); + assertNotNull("diff not found for field: " + name, d.getDiffForField(name)); + assertTrue(d.getDiffForField(name).hasDiff()); + assertTrue("unexpected field: " + name, expectedFrom.containsKey(name)); + assertTrue("unexpected field: " + name, expectedTo.containsKey(name)); + assertEquals(expectedFrom.get(name), d.getDiffForField(name).from()); + assertEquals(expectedTo.get(name), d.getDiffForField(name).to()); + } + } + + @Test + public void testConfigDiff_specialSenders() { + // these two start the same + ZenModeConfig c1 = new ZenModeConfig(); + ZenModeConfig c2 = new ZenModeConfig(); + + // set c1 and c2 to have some different senders + c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR; + c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT; + c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; + c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE; + + ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(d.hasDiff()); + + // Diff in top-level fields + assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff()); + assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff()); + + // Bonus testing of stringification of people senders and conversation senders + assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts")); + assertTrue(d.toString().contains("allowConversationsFrom:important->none")); + } + + @Test + public void testConfigDiff_hasRuleDiffs() { + // two default configs + ZenModeConfig c1 = new ZenModeConfig(); + ZenModeConfig c2 = new ZenModeConfig(); + + // two initially-identical rules + ZenModeConfig.ZenRule r1 = makeRule(); + ZenModeConfig.ZenRule r2 = makeRule(); + + // one that will become a manual rule + ZenModeConfig.ZenRule m = makeRule(); + + // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted + c1.automaticRules.put(r1.id, r1); + ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(deleteRule.hasDiff()); + assertNotNull(deleteRule.getAllAutomaticRuleDiffs()); + assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId")); + assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved()); + + // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule + r2.component = null; + r2.pkg = "different"; + c2.manualRule = m; + c2.automaticRules.put(r2.id, r2); + + // Expect diffs in both manual rule (added) and automatic rule (changed) + ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(changed.hasDiff()); + assertTrue(changed.getManualRuleDiff().hasDiff()); + + ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs(); + assertNotNull(automaticDiffs); + assertTrue(automaticDiffs.containsKey("ruleId")); + assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component")); + assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to()); + assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg")); + assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to()); + } + + // Helper methods for working with configs, policies, rules + // Just makes a zen rule with fields filled in + private ZenModeConfig.ZenRule makeRule() { + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = new ComponentName("a", "a"); + rule.component = new ComponentName("b", "b"); + rule.conditionId = new Uri.Builder().scheme("hello").build(); + rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE); + rule.enabled = true; + rule.creationTime = 123; + rule.id = "ruleId"; + rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + rule.modified = false; + rule.name = "name"; + rule.snoozing = true; + rule.pkg = "a"; + return rule; + } + + // Get the fields on which we would want to check a diff. The requirements are: not final or/ + // static (as these should/can never change), and not in a specific list that's exempted. + private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames) + throws SecurityException { + Field[] fields = c.getDeclaredFields(); + ArrayList<Field> out = new ArrayList<>(); + + for (Field field : fields) { + // Check for exempt reasons + int m = field.getModifiers(); + if (Modifier.isFinal(m) + || Modifier.isStatic(m) + || exemptNames.contains(field.getName())) { + continue; + } + out.add(field); + } + return out; + } + + // Generate a set of generic diffs for the specified two objects and the fields to generate + // diffs for, and store the results in the provided expectation maps to be able to check the + // output later. + private void generateFieldDiffs(Object a, Object b, List<Field> fields, + ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB) + throws Exception { + // different classes passed in means bad input + assertEquals(a.getClass(), b.getClass()); + + // Loop through fields for which we want to check diffs, set a diff and keep track of + // what we set. + for (Field f : fields) { + f.setAccessible(true); + // Just double-check also that the fields actually are for the class declared + assertEquals(f.getDeclaringClass(), a.getClass()); + Class t = f.getType(); + // handle the full set of primitive types first + if (boolean.class.equals(t)) { + f.setBoolean(a, true); + expectedA.put(f.getName(), true); + f.setBoolean(b, false); + expectedB.put(f.getName(), false); + } else if (int.class.equals(t)) { + // these are not actually valid going to be valid for arbitrary int enum fields, but + // we just put something in there regardless. + f.setInt(a, 2); + expectedA.put(f.getName(), 2); + f.setInt(b, 1); + expectedB.put(f.getName(), 1); + } else if (long.class.equals(t)) { + f.setLong(a, 200L); + expectedA.put(f.getName(), 200L); + f.setLong(b, 100L); + expectedB.put(f.getName(), 100L); + } else if (t.isPrimitive()) { + // This method doesn't yet handle other primitive types. If the relevant diff + // classes gain new fields of these types, please add another clause here. + fail("primitive type not handled by generateFieldDiffs: " + t.getName()); + } else if (String.class.equals(t)) { + f.set(a, "string1"); + expectedA.put(f.getName(), "string1"); + f.set(b, "string2"); + expectedB.put(f.getName(), "string2"); + } else { + // catch-all for other types: have the field be "added" + f.set(a, null); + expectedA.put(f.getName(), null); + try { + f.set(b, t.getDeclaredConstructor().newInstance()); + expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance()); + } catch (Exception e) { + // No default constructor, or blithely attempting to construct something doesn't + // work for some reason. If the default value isn't null, then keep it. + if (f.get(b) != null) { + expectedB.put(f.getName(), f.get(b)); + } else { + // If we can't even rely on that, fail. Have the test-writer special case + // something, as this is not able to be genericized. + fail("could not generically construct value for field: " + f.getName()); + } + } + } + } + } +} 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 6f9798ea7d69..b2a54010e75e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -69,8 +69,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AutomaticZenRule; @@ -78,7 +76,6 @@ import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.ContentResolver; -import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -90,7 +87,6 @@ import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.VolumePolicy; import android.net.Uri; -import android.os.Binder; import android.os.Process; import android.os.UserHandle; import android.provider.Settings; @@ -98,6 +94,7 @@ import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ScheduleInfo; +import android.service.notification.ZenModeDiff; import android.service.notification.ZenPolicy; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -877,7 +874,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL); assertEquals("Config mismatch: current vs expected: " - + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig); + + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected, + mZenModeHelperSpy.mConfig); } @Test @@ -1046,7 +1044,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10); assertEquals( - "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual); + "Config mismatch: current vs expected: " + + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual); assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11)); } @@ -1062,7 +1061,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM); assertEquals("Config mismatch: current vs original: " - + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig); + + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original), + original, mZenModeHelperSpy.mConfig); assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode()); } @@ -1083,8 +1083,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10); expected.user = 10; - assertEquals( - "Config mismatch: current vs original: " + actual.diff(expected), expected, actual); + assertEquals("Config mismatch: current vs original: " + + new ZenModeDiff.ConfigDiff(actual, expected), + expected, actual); assertEquals(expected.hashCode(), actual.hashCode()); expected.user = 0; assertNotEquals(expected, mZenModeHelperSpy.mConfig); diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp index 986fb71afa2d..e704ebf32270 100644 --- a/services/tests/voiceinteractiontests/Android.bp +++ b/services/tests/voiceinteractiontests/Android.bp @@ -40,6 +40,7 @@ android_test { "platform-test-annotations", "services.core", "services.voiceinteraction", + "services.soundtrigger", "servicestests-core-utils", "servicestests-utils-mockito-extended", "truth-prebuilt", 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 341b331b74e0..8f2b470908c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -83,6 +83,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED; +import static com.android.server.wm.ActivityRecord.LAUNCH_SOURCE_TYPE_HOME; import static com.android.server.wm.ActivityRecord.State.DESTROYED; import static com.android.server.wm.ActivityRecord.State.DESTROYING; import static com.android.server.wm.ActivityRecord.State.FINISHING; @@ -3688,6 +3689,23 @@ public class ActivityRecordTests extends WindowTestsBase { assertTrue(activity.inTransition()); } + /** + * Verifies the task is moved to back when back pressed if the root activity was originally + * started from Launcher. + */ + @Test + public void testMoveTaskToBackWhenStartedFromLauncher() { + final Task task = createTask(mDisplayContent); + final ActivityRecord ar = createActivityRecord(task); + task.realActivity = ar.mActivityComponent; + ar.intent.setAction(Intent.ACTION_MAIN); + ar.intent.addCategory(Intent.CATEGORY_LAUNCHER); + doReturn(true).when(ar).isLaunchSourceType(eq(LAUNCH_SOURCE_TYPE_HOME)); + + mAtm.mActivityClientController.onBackPressed(ar.token, null /* callback */); + verify(task).moveTaskToBack(any()); + } + private ICompatCameraControlCallback getCompatCameraControlCallback() { return new ICompatCameraControlCallback.Stub() { @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index ba9f809e9a2a..7330411d1dd7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +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_UNSPECIFIED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; @@ -1063,6 +1064,51 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); } + private void updateAllDisplayContentAndRotation(DisplayContent dc) { + // NB updateOrientation will not revert the user orientation until a settings change + // takes effect. + dc.updateOrientation(); + dc.onDisplayChanged(dc); + dc.mWmService.updateRotation(true /* alwaysSendConfiguration */, + false /* forceRelayout */); + waitUntilHandlersIdle(); + } + + @Test + public void testNoSensorRevert() { + final DisplayContent dc = mDisplayContent; + spyOn(dc); + doReturn(true).when(dc).getIgnoreOrientationRequest(); + final DisplayRotation dr = dc.getDisplayRotation(); + spyOn(dr); + doReturn(false).when(dr).useDefaultSettingsProvider(); + final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); + app.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, app); + + assertFalse(dc.getRotationReversionController().isAnyOverrideActive()); + dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, + ROTATION_90); + updateAllDisplayContentAndRotation(dc); + assertEquals(ROTATION_90, dc.getDisplayRotation() + .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_90)); + + app.setOrientation(SCREEN_ORIENTATION_NOSENSOR); + updateAllDisplayContentAndRotation(dc); + assertTrue(dc.getRotationReversionController().isAnyOverrideActive()); + assertEquals(ROTATION_0, dc.getRotation()); + + app.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + updateAllDisplayContentAndRotation(dc); + assertFalse(dc.getRotationReversionController().isAnyOverrideActive()); + assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, + dc.getDisplayRotation().getUserRotationMode()); + assertEquals(ROTATION_90, dc.getDisplayRotation().getUserRotation()); + assertEquals(ROTATION_90, dc.getDisplayRotation() + .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_0)); + dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, + ROTATION_0); + } + @Test public void testOnDescendantOrientationRequestChanged() { final DisplayContent dc = createNewDisplay(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index c2b3783b7311..a3117269eb01 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -365,6 +365,23 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { } @Test + public void testCameraDisconnected_revertRotationAndRefresh() throws Exception { + configureActivityAndDisplay(SCREEN_ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE); + // Open camera and test for compat treatment + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); + assertEquals(mDisplayRotationCompatPolicy.getOrientation(), + SCREEN_ORIENTATION_LANDSCAPE); + assertActivityRefreshRequested(/* refreshRequested */ true); + // Close camera and test for revert + mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); + callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); + assertEquals(mDisplayRotationCompatPolicy.getOrientation(), + SCREEN_ORIENTATION_UNSPECIFIED); + assertActivityRefreshRequested(/* refreshRequested */ true); + } + + @Test public void testGetOrientation_cameraConnectionClosed_returnUnspecified() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 19a1eddb4da7..4b2d1071d113 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -115,6 +115,7 @@ public class DisplayRotationTests { private static WindowManagerService sMockWm; private DisplayContent mMockDisplayContent; + private DisplayRotationReversionController mMockDisplayRotationReversionController; private DisplayPolicy mMockDisplayPolicy; private DisplayAddress mMockDisplayAddress; private Context mMockContext; @@ -1409,6 +1410,10 @@ public class DisplayRotationTests { when(mMockContext.getResources().getBoolean( com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride)) .thenReturn(mSupportHalfFoldAutoRotateOverride); + mMockDisplayRotationReversionController = + mock(DisplayRotationReversionController.class); + when(mMockDisplayContent.getRotationReversionController()) + .thenReturn(mMockDisplayRotationReversionController); mMockResolver = mock(ContentResolver.class); when(mMockContext.getContentResolver()).thenReturn(mMockResolver); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index c131c84a50ce..7092b0b5ac34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -104,7 +104,6 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { when(mMockRunner.asBinder()).thenReturn(new Binder()); mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks, DEFAULT_DISPLAY)); - mController.mShouldAttachNavBarToAppDuringTransition = false; mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask(); assertNotNull(mRootHomeTask); } @@ -814,13 +813,13 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { } private void setupForShouldAttachNavBarDuringTransition() { - mController.mShouldAttachNavBarToAppDuringTransition = true; final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar")); mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs); mWm.setRecentsAnimationController(mController); doReturn(navBar).when(mController).getNavigationBarWindow(); final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy()); doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy(); + doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition(); } private static void initializeRecentsAnimationController(RecentsAnimationController controller, 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 8e91ca28fcf1..77efc4b0d561 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java @@ -42,6 +42,8 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; +import com.android.server.testutils.TestHandler; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -371,6 +373,49 @@ public class SyncEngineTests extends WindowTestsBase { mAppWindow.removeImmediately(); } + @Test + public void testQueueSyncSet() { + final TestHandler testHandler = new TestHandler(null); + TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer mockWC2 = new TestWindowContainer(mWm, true /* waiter */); + + final BLASTSyncEngine bse = createTestBLASTSyncEngine(testHandler); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = startSyncSet(bse, listener); + bse.addToSyncSet(id, mockWC); + bse.setReady(id); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(eq(id), notNull()); + + final int[] nextId = new int[]{-1}; + bse.queueSyncSet( + () -> nextId[0] = startSyncSet(bse, listener), + () -> { + bse.setReady(nextId[0]); + bse.addToSyncSet(nextId[0], mockWC2); + }); + + // Make sure it is queued + assertEquals(-1, nextId[0]); + + // Finish the original sync and see that we've started a new sync-set immediately but + // that the readiness was posted. + mockWC.onSyncFinishedDrawing(); + verify(mWm.mWindowPlacerLocked).requestTraversal(); + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + + assertTrue(nextId[0] != -1); + assertFalse(bse.isReady(nextId[0])); + + // now make sure the applySync callback was posted. + testHandler.flush(); + assertTrue(bse.isReady(nextId[0])); + } + static int startSyncSet(BLASTSyncEngine engine, BLASTSyncEngine.TransactionReadyListener listener) { return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test"); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 90506d4f8651..43b429c76749 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1404,19 +1404,17 @@ public class TransitionTests extends WindowTestsBase { // We are now going to simulate closing task1 to return back to (open) task2. final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE); - closeTransition.collectExistenceChange(task1); - closeTransition.collectExistenceChange(activity1); closeTransition.collectExistenceChange(task2); closeTransition.collectExistenceChange(activity2); closeTransition.setTransientLaunch(activity2, task1); final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1); assertNotNull(task1ChangeInfo); assertTrue(task1ChangeInfo.hasChanged()); + // Make sure the unrelated activity is NOT collected. final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1); - assertNotNull(activity1ChangeInfo); - assertTrue(activity1ChangeInfo.hasChanged()); + assertNull(activity1ChangeInfo); // No need to wait for the activity in transient hide task. - assertTrue(activity1.isSyncFinished()); + assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState); activity1.setVisibleRequested(false); activity2.setVisibleRequested(true); @@ -1444,6 +1442,7 @@ public class TransitionTests extends WindowTestsBase { } } }); + assertTrue(activity1.isVisible()); controller.finishTransition(closeTransition); assertTrue(wasInFinishingTransition[0]); assertNull(controller.mFinishingTransition); @@ -1452,6 +1451,7 @@ public class TransitionTests extends WindowTestsBase { assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState()); // Because task1 is occluded by task2, finishTransition should make activity1 invisible. assertFalse(activity1.isVisibleRequested()); + // Make sure activity1 visibility was committed assertFalse(activity1.isVisible()); assertFalse(activity1.app.hasActivityInVisibleTask()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 7e3ec55f262a..f85cdf0b5035 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -77,6 +77,7 @@ import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; @@ -886,7 +887,11 @@ class WindowTestsBase extends SystemServiceTestsBase { } BLASTSyncEngine createTestBLASTSyncEngine() { - return new BLASTSyncEngine(mWm) { + return createTestBLASTSyncEngine(mWm.mH); + } + + BLASTSyncEngine createTestBLASTSyncEngine(Handler handler) { + return new BLASTSyncEngine(mWm, handler) { @Override void scheduleTimeout(SyncGroup s, long timeoutMs) { // Disable timeout. diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 6cf2b2d7a31e..74ba45c130e3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -500,7 +500,6 @@ public class ZOrderingTests extends WindowTestsBase { RecentsAnimationController controller = new RecentsAnimationController( mWm, mockRunner, null, displayId); spyOn(controller); - controller.mShouldAttachNavBarToAppDuringTransition = true; doReturn(mNavBarWindow).when(controller).getNavigationBarWindow(); mWm.setRecentsAnimationController(controller); @@ -508,6 +507,10 @@ public class ZOrderingTests extends WindowTestsBase { spyOn(mDisplayContent.mInputMethodWindow); doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible(); + DisplayPolicy policy = mDisplayContent.getDisplayPolicy(); + spyOn(policy); + doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition(); + // create home activity Task rootHomeTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask(); final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService) diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java index 337e1f92050c..7fe8582f96de 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java @@ -27,6 +27,8 @@ import android.util.Slog; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.audio.AudioService; +import java.util.Arrays; + /** * Represents the ALSA specification, and attributes of an ALSA device. */ @@ -36,17 +38,21 @@ public final class UsbAlsaDevice { private final int mCardNum; private final int mDeviceNum; + private final String mAlsaCardDeviceString; private final String mDeviceAddress; - private final boolean mHasOutput; - private final boolean mHasInput; - private final boolean mIsInputHeadset; - private final boolean mIsOutputHeadset; - private final boolean mIsDock; + // The following two constant will be used as index to access arrays. + private static final int INPUT = 0; + private static final int OUTPUT = 1; + private static final int NUM_DIRECTIONS = 2; + private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"}; + private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS]; - private boolean mSelected = false; - private int mOutputState; - private int mInputState; + private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS]; + private final boolean mIsDock; + private final int[] mDeviceType = new int[NUM_DIRECTIONS]; + private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS]; + private int[] mState = new int[NUM_DIRECTIONS]; private UsbAlsaJackDetector mJackDetector; private IAudioService mAudioService; @@ -60,11 +66,13 @@ public final class UsbAlsaDevice { mCardNum = card; mDeviceNum = device; mDeviceAddress = deviceAddress; - mHasOutput = hasOutput; - mHasInput = hasInput; - mIsInputHeadset = isInputHeadset; - mIsOutputHeadset = isOutputHeadset; + mHasDevice[OUTPUT] = hasOutput; + mHasDevice[INPUT] = hasInput; + mIsHeadset[INPUT] = isInputHeadset; + mIsHeadset[OUTPUT] = isOutputHeadset; mIsDock = isDock; + initDeviceType(); + mAlsaCardDeviceString = getAlsaCardDeviceString(); } /** @@ -104,28 +112,28 @@ public final class UsbAlsaDevice { * @return true if the device supports output. */ public boolean hasOutput() { - return mHasOutput; + return mHasDevice[OUTPUT]; } /** * @return true if the device supports input (recording). */ public boolean hasInput() { - return mHasInput; + return mHasDevice[INPUT]; } /** - * @return true if the device is a headset for purposes of input. + * @return true if the device is a headset for purposes of output. */ - public boolean isInputHeadset() { - return mIsInputHeadset; + public boolean isOutputHeadset() { + return mIsHeadset[OUTPUT]; } /** - * @return true if the device is a headset for purposes of output. + * @return true if the device is a headset for purposes of input. */ - public boolean isOutputHeadset() { - return mIsOutputHeadset; + public boolean isInputHeadset() { + return mIsHeadset[INPUT]; } /** @@ -157,6 +165,9 @@ public final class UsbAlsaDevice { /** Begins a jack-detection thread. */ private synchronized void startJackDetect() { + if (mJackDetector != null) { + return; + } // If no jack detect capabilities exist, mJackDetector will be null. mJackDetector = UsbAlsaJackDetector.startJackDetect(this); } @@ -171,75 +182,152 @@ public final class UsbAlsaDevice { /** Start using this device as the selected USB Audio Device. */ public synchronized void start() { - mSelected = true; - mInputState = 0; - mOutputState = 0; + startInput(); + startOutput(); + } + + /** Start using this device as the selected USB input device. */ + public synchronized void startInput() { + startDevice(INPUT); + } + + /** Start using this device as selected USB output device. */ + public synchronized void startOutput() { + startDevice(OUTPUT); + } + + private void startDevice(int direction) { + if (mIsSelected[direction]) { + return; + } + mIsSelected[direction] = true; + mState[direction] = 0; startJackDetect(); - updateWiredDeviceConnectionState(true); + updateWiredDeviceConnectionState(direction, true /*enable*/); } /** Stop using this device as the selected USB Audio Device. */ public synchronized void stop() { - stopJackDetect(); - updateWiredDeviceConnectionState(false); - mSelected = false; + stopInput(); + stopOutput(); } - /** Updates AudioService with the connection state of the alsaDevice. - * Checks ALSA Jack state for inputs and outputs before reporting. - */ - public synchronized void updateWiredDeviceConnectionState(boolean enable) { - if (!mSelected) { - Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!"); + /** Stop using this device as the selected USB input device. */ + public synchronized void stopInput() { + if (!mIsSelected[INPUT]) { return; } - String alsaCardDeviceString = getAlsaCardDeviceString(); - if (alsaCardDeviceString == null) { + if (!mIsSelected[OUTPUT]) { + // Stop jack detection when both input and output are stopped + stopJackDetect(); + } + updateInputWiredDeviceConnectionState(false /*enable*/); + mIsSelected[INPUT] = false; + } + + /** Stop using this device as the selected USB output device. */ + public synchronized void stopOutput() { + if (!mIsSelected[OUTPUT]) { return; } - try { - // Output Device - if (mHasOutput) { - int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET - : (mIsOutputHeadset - ? AudioSystem.DEVICE_OUT_USB_HEADSET - : AudioSystem.DEVICE_OUT_USB_DEVICE); - if (DEBUG) { - Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device) - + " addr:" + alsaCardDeviceString - + " name:" + mDeviceName); - } - boolean connected = isOutputJackConnected(); - Slog.i(TAG, "OUTPUT JACK connected: " + connected); - int outputState = (enable && connected) ? 1 : 0; - if (outputState != mOutputState) { - mOutputState = outputState; - AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, - alsaCardDeviceString, mDeviceName); - mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG); - } - } + if (!mIsSelected[INPUT]) { + // Stop jack detection when both input and output are stopped + stopJackDetect(); + } + updateOutputWiredDeviceConnectionState(false /*enable*/); + mIsSelected[OUTPUT] = false; + } + + private void initDeviceType() { + mDeviceType[INPUT] = mHasDevice[INPUT] + ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET + : AudioSystem.DEVICE_IN_USB_DEVICE) + : AudioSystem.DEVICE_NONE; + mDeviceType[OUTPUT] = mHasDevice[OUTPUT] + ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET + : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET + : AudioSystem.DEVICE_OUT_USB_DEVICE)) + : AudioSystem.DEVICE_NONE; + } - // Input Device - if (mHasInput) { - int device = mIsInputHeadset - ? AudioSystem.DEVICE_IN_USB_HEADSET - : AudioSystem.DEVICE_IN_USB_DEVICE; - boolean connected = isInputJackConnected(); - Slog.i(TAG, "INPUT JACK connected: " + connected); - int inputState = (enable && connected) ? 1 : 0; - if (inputState != mInputState) { - mInputState = inputState; - AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, - alsaCardDeviceString, mDeviceName); - mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG); - } + /** + * @return the output device type that will be used to notify AudioService about device + * connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE} + * will be returned. + */ + public int getOutputDeviceType() { + return mDeviceType[OUTPUT]; + } + + /** + * @return the input device type that will be used to notify AudioService about device + * connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE} + * will be returned. + */ + public int getInputDeviceType() { + return mDeviceType[INPUT]; + } + + private boolean updateWiredDeviceConnectionState(int direction, boolean enable) { + if (!mIsSelected[direction]) { + Slog.e(TAG, "Updating wired device connection state on unselected device"); + return false; + } + if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) { + Slog.d(TAG, + "Unable to set device connection state as " + DIRECTION_STR[direction] + + " device type is none"); + return false; + } + if (mAlsaCardDeviceString == null) { + Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection " + + "state failed as alsa card device string is null"); + return false; + } + if (DEBUG) { + Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction]) + + " addr:" + mAlsaCardDeviceString + + " name:" + mDeviceName); + } + boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected(); + Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected); + int state = (enable && connected) ? 1 : 0; + if (state != mState[direction]) { + mState[direction] = state; + AudioDeviceAttributes attributes = new AudioDeviceAttributes( + mDeviceType[direction], mAlsaCardDeviceString, mDeviceName); + try { + mAudioService.setWiredDeviceConnectionState(attributes, state, TAG); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for " + + DIRECTION_STR[direction]); + return false; } - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState"); } + return true; } + /** + * Notify AudioService about the input device connection state. + * + * @param enable true to notify the device as connected. + * @return true only when it successfully notifies AudioService about the device + * connection state. + */ + public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) { + return updateWiredDeviceConnectionState(INPUT, enable); + } + + /** + * Notify AudioService about the output device connection state. + * + * @param enable true to notify the device as connected. + * @return true only when it successfully notifies AudioService about the device + * connection state. + */ + public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) { + return updateWiredDeviceConnectionState(OUTPUT, enable); + } /** * @Override @@ -249,8 +337,8 @@ public final class UsbAlsaDevice { return "UsbAlsaDevice: [card: " + mCardNum + ", device: " + mDeviceNum + ", name: " + mDeviceName - + ", hasOutput: " + mHasOutput - + ", hasInput: " + mHasInput + "]"; + + ", hasOutput: " + mHasDevice[OUTPUT] + + ", hasInput: " + mHasDevice[INPUT] + "]"; } /** @@ -262,8 +350,8 @@ public final class UsbAlsaDevice { dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum); dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum); dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName); - dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput); - dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput); + dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]); + dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]); dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress); dump.end(token); @@ -294,10 +382,8 @@ public final class UsbAlsaDevice { UsbAlsaDevice other = (UsbAlsaDevice) obj; return (mCardNum == other.mCardNum && mDeviceNum == other.mDeviceNum - && mHasOutput == other.mHasOutput - && mHasInput == other.mHasInput - && mIsInputHeadset == other.mIsInputHeadset - && mIsOutputHeadset == other.mIsOutputHeadset + && Arrays.equals(mHasDevice, other.mHasDevice) + && Arrays.equals(mIsHeadset, other.mIsHeadset) && mIsDock == other.mIsDock); } @@ -310,10 +396,10 @@ public final class UsbAlsaDevice { int result = 1; result = prime * result + mCardNum; result = prime * result + mDeviceNum; - result = prime * result + (mHasOutput ? 0 : 1); - result = prime * result + (mHasInput ? 0 : 1); - result = prime * result + (mIsInputHeadset ? 0 : 1); - result = prime * result + (mIsOutputHeadset ? 0 : 1); + result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1); + result = prime * result + (mHasDevice[INPUT] ? 0 : 1); + result = prime * result + (mIsHeadset[INPUT] ? 0 : 1); + result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1); result = prime * result + (mIsDock ? 0 : 1); return result; diff --git a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java index c4988478df71..d4f0b59dd7f2 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java @@ -81,7 +81,8 @@ public final class UsbAlsaJackDetector implements Runnable { if (mStopJackDetect) { return false; } - mAlsaDevice.updateWiredDeviceConnectionState(true); + mAlsaDevice.updateOutputWiredDeviceConnectionState(true); + mAlsaDevice.updateInputWiredDeviceConnectionState(true); } return true; } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index aa1d556d02d3..99881e194b07 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -20,12 +20,14 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.usb.UsbDevice; +import android.media.AudioManager; import android.media.IAudioService; import android.media.midi.MidiDeviceInfo; import android.os.Bundle; import android.os.FileObserver; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Settings; import android.service.usb.UsbAlsaManagerProto; import android.util.Slog; @@ -42,6 +44,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Stack; /** * UsbAlsaManager manages USB audio and MIDI devices. @@ -51,8 +54,9 @@ public final class UsbAlsaManager { private static final boolean DEBUG = false; // Flag to turn on/off multi-peripheral select mode - // Set to true to have single-device-only mode - private static final boolean mIsSingleMode = true; + // Set to true to have multi-devices mode + private static final boolean IS_MULTI_MODE = SystemProperties.getBoolean( + "ro.audio.multi_usb_mode", false /*def*/); private static final String ALSA_DIRECTORY = "/dev/snd/"; @@ -70,7 +74,11 @@ public final class UsbAlsaManager { // this is needed to map USB devices to ALSA Audio Devices, especially to remove an // ALSA device when we are notified that its associated USB device has been removed. private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>(); - private UsbAlsaDevice mSelectedDevice; + // A map from device type to attached devices. Given the audio framework only supports + // single device connection per device type, only the last attached device will be + // connected to audio framework. Once the last device is removed, previous device can + // be connected to audio framework. + private HashMap<Integer, Stack<UsbAlsaDevice>> mAttachedDevices = new HashMap<>(); // // Device Denylist @@ -162,11 +170,6 @@ public final class UsbAlsaManager { Slog.d(TAG, "selectAlsaDevice() " + alsaDevice); } - // This must be where an existing USB audio device is deselected.... (I think) - if (mIsSingleMode && mSelectedDevice != null) { - deselectAlsaDevice(); - } - // FIXME Does not yet handle the case where the setting is changed // after device connection. Ideally we should handle the settings change // in SettingsObserver. Here we should log that a USB device is connected @@ -178,21 +181,18 @@ public final class UsbAlsaManager { return; } - mSelectedDevice = alsaDevice; alsaDevice.start(); + if (DEBUG) { Slog.d(TAG, "selectAlsaDevice() - done."); } } - private synchronized void deselectAlsaDevice() { + private synchronized void deselectAlsaDevice(UsbAlsaDevice selectedDevice) { if (DEBUG) { - Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice); - } - if (mSelectedDevice != null) { - mSelectedDevice.stop(); - mSelectedDevice = null; + Slog.d(TAG, "deselectAlsaDevice() selectedDevice " + selectedDevice); } + selectedDevice.stop(); } private int getAlsaDeviceListIndexFor(String deviceAddress) { @@ -204,32 +204,86 @@ public final class UsbAlsaManager { return -1; } - private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) { + private void addDeviceToAttachedDevicesMap(int deviceType, UsbAlsaDevice device) { + if (deviceType == AudioManager.DEVICE_NONE) { + Slog.i(TAG, "Ignore caching device as the type is NONE, device=" + device); + return; + } + Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType); + if (devices == null) { + mAttachedDevices.put(deviceType, new Stack<>()); + devices = mAttachedDevices.get(deviceType); + } + devices.push(device); + } + + private void addAlsaDevice(UsbAlsaDevice device) { + mAlsaDevices.add(0, device); + addDeviceToAttachedDevicesMap(device.getInputDeviceType(), device); + addDeviceToAttachedDevicesMap(device.getOutputDeviceType(), device); + } + + private void removeDeviceFromAttachedDevicesMap(int deviceType, UsbAlsaDevice device) { + Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType); + if (devices == null) { + return; + } + devices.remove(device); + if (devices.isEmpty()) { + mAttachedDevices.remove(deviceType); + } + } + + private UsbAlsaDevice removeAlsaDevice(String deviceAddress) { int index = getAlsaDeviceListIndexFor(deviceAddress); if (index > -1) { - return mAlsaDevices.remove(index); + UsbAlsaDevice device = mAlsaDevices.remove(index); + removeDeviceFromAttachedDevicesMap(device.getOutputDeviceType(), device); + removeDeviceFromAttachedDevicesMap(device.getInputDeviceType(), device); + return device; } else { return null; } } - /* package */ UsbAlsaDevice selectDefaultDevice() { + private UsbAlsaDevice selectDefaultDevice(int deviceType) { if (DEBUG) { - Slog.d(TAG, "selectDefaultDevice()"); + Slog.d(TAG, "selectDefaultDevice():" + deviceType); } - if (mAlsaDevices.size() > 0) { - UsbAlsaDevice alsaDevice = mAlsaDevices.get(0); - if (DEBUG) { - Slog.d(TAG, " alsaDevice:" + alsaDevice); - } - if (alsaDevice != null) { - selectAlsaDevice(alsaDevice); - } - return alsaDevice; - } else { + Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType); + if (devices == null || devices.isEmpty()) { return null; } + UsbAlsaDevice alsaDevice = devices.peek(); + Slog.d(TAG, "select default device:" + alsaDevice); + if (AudioManager.isInputDevice(deviceType)) { + alsaDevice.startInput(); + } else { + alsaDevice.startOutput(); + } + return alsaDevice; + } + + private void deselectCurrentDevice(int deviceType) { + if (DEBUG) { + Slog.d(TAG, "deselectCurrentDevice():" + deviceType); + } + if (deviceType == AudioManager.DEVICE_NONE) { + return; + } + + Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType); + if (devices == null || devices.isEmpty()) { + return; + } + UsbAlsaDevice alsaDevice = devices.peek(); + Slog.d(TAG, "deselect current device:" + alsaDevice); + if (AudioManager.isInputDevice(deviceType)) { + alsaDevice.stopInput(); + } else { + alsaDevice.stopOutput(); + } } /* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice, @@ -246,6 +300,7 @@ public final class UsbAlsaManager { AlsaCardsParser.AlsaCardRecord cardRec = mCardsParser.findCardNumFor(deviceAddress); if (cardRec == null) { + Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress); return; } @@ -275,12 +330,19 @@ public final class UsbAlsaManager { new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/, deviceAddress, hasOutput, hasInput, isInputHeadset, isOutputHeadset, isDock); - if (alsaDevice != null) { - alsaDevice.setDeviceNameAndDescription( - cardRec.getCardName(), cardRec.getCardDescription()); - mAlsaDevices.add(0, alsaDevice); - selectAlsaDevice(alsaDevice); + alsaDevice.setDeviceNameAndDescription( + cardRec.getCardName(), cardRec.getCardDescription()); + if (IS_MULTI_MODE) { + deselectCurrentDevice(alsaDevice.getInputDeviceType()); + deselectCurrentDevice(alsaDevice.getOutputDeviceType()); + } else { + // At single mode, the first device is the selected device. + if (!mAlsaDevices.isEmpty()) { + deselectAlsaDevice(mAlsaDevices.get(0)); + } } + addAlsaDevice(alsaDevice); + selectAlsaDevice(alsaDevice); } addMidiDevice(deviceAddress, usbDevice, parser, cardRec); @@ -346,12 +408,20 @@ public final class UsbAlsaManager { } // Audio - UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress); + UsbAlsaDevice alsaDevice = removeAlsaDevice(deviceAddress); Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice); - if (alsaDevice != null && alsaDevice == mSelectedDevice) { + if (alsaDevice != null) { waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/); - deselectAlsaDevice(); - selectDefaultDevice(); // if there any external devices left, select one of them + deselectAlsaDevice(alsaDevice); + if (IS_MULTI_MODE) { + selectDefaultDevice(alsaDevice.getOutputDeviceType()); + selectDefaultDevice(alsaDevice.getInputDeviceType()); + } else { + // If there are any external devices left, select the latest attached one + if (!mAlsaDevices.isEmpty() && mAlsaDevices.get(0) != null) { + selectAlsaDevice(mAlsaDevices.get(0)); + } + } } // MIDI @@ -362,7 +432,6 @@ public final class UsbAlsaManager { } logDevices("usbDeviceRemoved()"); - } /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) { diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp index 7332d2d8b0f6..de8d1440e6ac 100644 --- a/services/voiceinteraction/Android.bp +++ b/services/voiceinteraction/Android.bp @@ -9,11 +9,60 @@ package { filegroup { name: "services.voiceinteraction-sources", - srcs: ["java/**/*.java"], + srcs: ["java/com/android/server/voiceinteraction/*.java"], path: "java", visibility: ["//frameworks/base/services"], } +filegroup { + name: "services.soundtrigger_middleware-sources", + srcs: ["java/com/android/server/soundtrigger_middleware/*.java"], + path: "java", + visibility: ["//visibility:private"], +} + +filegroup { + name: "services.soundtrigger_service-sources", + srcs: ["java/com/android/server/soundtrigger/*.java"], + path: "java", + visibility: ["//visibility:private"], +} + +filegroup { + name: "services.soundtrigger-sources", + srcs: [ + ":services.soundtrigger_service-sources", + ":services.soundtrigger_middleware-sources", + ], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.soundtrigger_middleware", + defaults: ["platform_service_defaults"], + srcs: [":services.soundtrigger_middleware-sources"], + libs: [ + "services.core", + ], + static_libs: [ + "android.hardware.soundtrigger-V2.3-java", + ], + visibility: ["//visibility/base/services/tests/voiceinteraction"], +} + +java_library_static { + name: "services.soundtrigger", + defaults: ["platform_service_defaults"], + srcs: [":services.soundtrigger_service-sources"], + libs: [ + "services.core", + ], + static_libs: [ + "services.soundtrigger_middleware", + ], +} + java_library_static { name: "services.voiceinteraction", defaults: ["platform_service_defaults"], diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING index 5fe1c8d2ecb0..f098155a9bf7 100644 --- a/services/voiceinteraction/TEST_MAPPING +++ b/services/voiceinteraction/TEST_MAPPING @@ -5,6 +5,9 @@ "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest" } ] }, diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 00974ac8f1f7..1bbea89f5acb 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -89,6 +89,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.ISoundTriggerService; import com.android.internal.app.ISoundTriggerSession; +import com.android.server.SoundTriggerInternal; import com.android.server.SystemService; import com.android.server.utils.EventLogger; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index dd0fa0ba6adf..1d7b966bab51 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -99,11 +99,11 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.SoundTriggerInternal; import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.LegacyPermissionManagerInternal; -import com.android.server.soundtrigger.SoundTriggerInternal; import com.android.server.utils.Slogf; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.wm.ActivityTaskManagerInternal; diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java index f3ef834168b5..52ff90f38113 100644 --- a/telecomm/java/android/telecom/CallAttributes.java +++ b/telecomm/java/android/telecom/CallAttributes.java @@ -59,7 +59,10 @@ public final class CallAttributes implements Parcelable { public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities"; /** @hide **/ - public static final String CALLER_PID = "CallerPid"; + public static final String CALLER_PID_KEY = "CallerPid"; + + /** @hide **/ + public static final String CALLER_UID_KEY = "CallerUid"; private CallAttributes(@NonNull PhoneAccountHandle phoneAccountHandle, @NonNull CharSequence displayName, diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index e39af5aa3327..9dd2a61671ec 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -2682,71 +2682,76 @@ public class TelecomManager { } /** - * Reports a new call with the specified {@link CallAttributes} to the telecom service. This - * method can be used to report both incoming and outgoing calls. By reporting the call, the - * system is aware of the call and can provide updates on services (ex. Another device wants to - * disconnect the call) or events (ex. a new Bluetooth route became available). - * + * Add a call to the Android system service Telecom. This allows the system to start tracking an + * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready + * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor, + * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}. * <p> - * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or - * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API - * will asynchronously provide an update on whether the new call was added successfully via - * an {@link OutcomeReceiver}. Additionally, callbacks will run on the executor thread that was - * passed in. - * * <p> - * Note: Only packages that register with + * <p> + * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a + * valid call and are posting a {@link android.app.Notification.CallStyle} notification. + * When your application is given foreground execution priority, your app is treated as a + * foreground service. Foreground execution priority will prevent the + * {@link android.app.ActivityManager} from killing your application when it is placed the + * background. Foreground execution priority is removed from your app when all of your app's + * calls terminate or your app no longer posts a valid notification. + * <p> + * <p> + * <p> + * <b>Note</b>: Only packages that register with * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS} * can utilize this API. {@link PhoneAccount}s that set the capabilities * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION}, * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER}, * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} * are not supported and will cause an exception to be thrown. - * * <p> - * Usage example: + * <p> + * <p> + * <b>Usage example:</b> * <pre> - * - * // An app should first define their own construct of a Call that overrides all the - * // {@link CallControlCallback}s and {@link CallEventCallback}s - * private class MyVoipCall { - * public String callId = ""; - * - * public CallControlCallEventCallback handshakes = new - * CallControlCallEventCallback() { - * // override/ implement all {@link CallControlCallback}s + * // Its up to your app on how you want to wrap the objects. One such implementation can be: + * class MyVoipCall { + * ... + * public CallControlCallEventCallback handshakes = new CallControlCallback() { + * ... * } - * public CallEventCallback events = new - * CallEventCallback() { - * // override/ implement all {@link CallEventCallback}s - * } - * public MyVoipCall(String id){ - * callId = id; - * } * - * PhoneAccountHandle handle = new PhoneAccountHandle( - * new ComponentName("com.example.voip.app", - * "com.example.voip.app.NewCallActivity"), "123"); + * public CallEventCallback events = new CallEventCallback() { + * ... + * } * - * CallAttributes callAttributes = new CallAttributes.Builder(handle, - * CallAttributes.DIRECTION_OUTGOING, - * "John Smith", Uri.fromParts("tel", "123", null)) - * .build(); + * public MyVoipCall(String id){ + * ... + * } + * } * * MyVoipCall myFirstOutgoingCall = new MyVoipCall("1"); * - * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() { + * telecomManager.addCall(callAttributes, + * Runnable::run, + * new OutcomeReceiver() { * public void onResult(CallControl callControl) { - * // The call has been added successfully + * // The call has been added successfully. For demonstration + * // purposes, the call is disconnected immediately ... + * callControl.disconnect( + * new DisconnectCause(DisconnectCause.LOCAL) ) * } - * }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events); + * }, + * myFirstOutgoingCall.handshakes, + * myFirstOutgoingCall.events); * </pre> * - * @param callAttributes attributes of the new call (incoming or outgoing, address, etc. ) - * @param executor thread to run background CallEventCallback updates on - * @param pendingControl OutcomeReceiver that receives the result of addCall transaction - * @param handshakes object that overrides {@link CallControlCallback}s - * @param events object that overrides {@link CallEventCallback}s + * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.) + * @param executor execution context to run {@link CallControlCallback} updates on + * @param pendingControl Receives the result of addCall transaction. Upon success, a + * CallControl object is provided which can be used to do things like + * disconnect the call that was added. + * @param handshakes callback that receives <b>actionable</b> updates that originate from + * Telecom. + * @param events callback that receives <b>non</b>-actionable updates that originate + * from Telecom. */ @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS) @SuppressLint("SamShouldBeLast") diff --git a/core/java/com/android/internal/expresslog/Utils.java b/telephony/java/android/telephony/satellite/AntennaDirection.aidl index d82192f51662..c838f6fbb8ac 100644 --- a/core/java/com/android/internal/expresslog/Utils.java +++ b/telephony/java/android/telephony/satellite/AntennaDirection.aidl @@ -1,11 +1,11 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,8 +14,6 @@ * limitations under the License. */ -package com.android.internal.expresslog; +package android.telephony.satellite; -final class Utils { - static native long hashString(String stringToHash); -} +parcelable AntennaDirection; diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java new file mode 100644 index 000000000000..02b0bc7364a2 --- /dev/null +++ b/telephony/java/android/telephony/satellite/AntennaDirection.java @@ -0,0 +1,138 @@ +/* + * 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.telephony.satellite; + +import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Antenna direction is provided as X/Y/Z values corresponding to the direction of the antenna + * main lobe as a unit vector in CTIA coordinate system (as specified in Appendix A of Wireless + * device CTIA OTAn test plan). CTIA coordinate system is defined relative to device’s screen + * when the device is held in default portrait mode with screen facing the user: + * + * Z axis is vertical along the plane of the device with positive Z pointing up and negative z + * pointing towards bottom of the device + * Y axis is horizontal along the plane of the device with positive Y pointing towards right of + * the phone screen and negative Y pointing towards left + * X axis is orthogonal to the Y-Z plane (phone screen), pointing away from the phone screen for + * positive X and pointing away from back of the phone for negative X. + * @hide + */ +public final class AntennaDirection implements Parcelable { + /** Antenna x axis direction. */ + private float mX; + + /** Antenna y axis direction. */ + private float mY; + + /** Antenna z axis direction. */ + private float mZ; + + /** + * @hide + */ + @UnsupportedAppUsage + public AntennaDirection(float x, float y, float z) { + mX = x; + mY = y; + mZ = z; + } + + private AntennaDirection(Parcel in) { + readFromParcel(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeFloat(mX); + out.writeFloat(mY); + out.writeFloat(mZ); + } + + @NonNull + public static final Creator<AntennaDirection> CREATOR = + new Creator<>() { + @Override + public AntennaDirection createFromParcel(Parcel in) { + return new AntennaDirection(in); + } + + @Override + public AntennaDirection[] newArray(int size) { + return new AntennaDirection[size]; + } + }; + + @Override + @NonNull public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("X:"); + sb.append(mX); + sb.append(","); + + sb.append("Y:"); + sb.append(mY); + sb.append(","); + + sb.append("Z:"); + sb.append(mZ); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AntennaDirection that = (AntennaDirection) o; + return mX == that.mX + && mY == that.mY + && mZ == that.mZ; + } + + @Override + public int hashCode() { + return Objects.hash(mX, mY, mZ); + } + + public float getX() { + return mX; + } + + public float getY() { + return mY; + } + + public float getZ() { + return mZ; + } + + private void readFromParcel(Parcel in) { + mX = in.readFloat(); + mY = in.readFloat(); + mZ = in.readFloat(); + } +} diff --git a/keystore/java/android/security/GenerateRkpKeyException.java b/telephony/java/android/telephony/satellite/AntennaPosition.aidl index a2d65e4e7119..00525624329c 100644 --- a/keystore/java/android/security/GenerateRkpKeyException.java +++ b/telephony/java/android/telephony/satellite/AntennaPosition.aidl @@ -1,11 +1,11 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,18 +14,6 @@ * limitations under the License. */ -package android.security; +package android.telephony.satellite; -/** - * Thrown on problems in attempting to attest to a key using a remotely provisioned key. - * - * @hide - */ -public class GenerateRkpKeyException extends Exception { - - /** - * Constructs a new {@code GenerateRkpKeyException}. - */ - public GenerateRkpKeyException() { - } -} +parcelable AntennaPosition; diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java new file mode 100644 index 000000000000..eefc8b00f8e8 --- /dev/null +++ b/telephony/java/android/telephony/satellite/AntennaPosition.java @@ -0,0 +1,117 @@ +/* + * 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.telephony.satellite; + +import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Antenna Position received from satellite modem which gives information about antenna + * direction to be used with satellite communication and suggested device hold positions. + * @hide + */ +public final class AntennaPosition implements Parcelable { + /** Antenna direction used for satellite communication. */ + @NonNull AntennaDirection mAntennaDirection; + + /** Enum corresponding to device hold position to be used by the end user. */ + @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition; + + /** + * @hide + */ + @UnsupportedAppUsage + public AntennaPosition(@NonNull AntennaDirection antennaDirection, int suggestedHoldPosition) { + mAntennaDirection = antennaDirection; + mSuggestedHoldPosition = suggestedHoldPosition; + } + + private AntennaPosition(Parcel in) { + readFromParcel(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeParcelable(mAntennaDirection, flags); + out.writeInt(mSuggestedHoldPosition); + } + + @NonNull + public static final Creator<AntennaPosition> CREATOR = + new Creator<>() { + @Override + public AntennaPosition createFromParcel(Parcel in) { + return new AntennaPosition(in); + } + + @Override + public AntennaPosition[] newArray(int size) { + return new AntennaPosition[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AntennaPosition that = (AntennaPosition) o; + return Objects.equals(mAntennaDirection, that.mAntennaDirection) + && mSuggestedHoldPosition == that.mSuggestedHoldPosition; + } + + @Override + public int hashCode() { + return Objects.hash(mAntennaDirection, mSuggestedHoldPosition); + } + + @Override + @NonNull public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("antennaDirection:"); + sb.append(mAntennaDirection); + sb.append(","); + + sb.append("suggestedHoldPosition:"); + sb.append(mSuggestedHoldPosition); + return sb.toString(); + } + + @NonNull + public AntennaDirection getAntennaDirection() { + return mAntennaDirection; + } + + @SatelliteManager.DeviceHoldPosition + public int getSuggestedHoldPosition() { + return mSuggestedHoldPosition; + } + + private void readFromParcel(Parcel in) { + mAntennaDirection = in.readParcelable(AntennaDirection.class.getClassLoader(), + AntennaDirection.class); + mSuggestedHoldPosition = in.readInt(); + } +} diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java index 87c8db317195..00928904a4c4 100644 --- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java +++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java @@ -21,7 +21,10 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -44,15 +47,25 @@ public final class SatelliteCapabilities implements Parcelable { private int mMaxBytesPerOutgoingDatagram; /** + * Antenna Position received from satellite modem which gives information about antenna + * direction to be used with satellite communication and suggested device hold positions. + * Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition + */ + @NonNull + private Map<Integer, AntennaPosition> mAntennaPositionMap; + + /** * @hide */ @UnsupportedAppUsage public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, - boolean isPointingRequired, int maxBytesPerOutgoingDatagram) { + boolean isPointingRequired, int maxBytesPerOutgoingDatagram, + @NonNull Map<Integer, AntennaPosition> antennaPositionMap) { mSupportedRadioTechnologies = supportedRadioTechnologies == null ? new HashSet<>() : supportedRadioTechnologies; mIsPointingRequired = isPointingRequired; mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram; + mAntennaPositionMap = antennaPositionMap; } private SatelliteCapabilities(Parcel in) { @@ -77,6 +90,17 @@ public final class SatelliteCapabilities implements Parcelable { out.writeBoolean(mIsPointingRequired); out.writeInt(mMaxBytesPerOutgoingDatagram); + + if (mAntennaPositionMap != null && !mAntennaPositionMap.isEmpty()) { + int size = mAntennaPositionMap.size(); + out.writeInt(size); + for (Map.Entry<Integer, AntennaPosition> entry : mAntennaPositionMap.entrySet()) { + out.writeInt(entry.getKey()); + out.writeParcelable(entry.getValue(), flags); + } + } else { + out.writeInt(0); + } } @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() { @@ -109,11 +133,32 @@ public final class SatelliteCapabilities implements Parcelable { sb.append(mIsPointingRequired); sb.append(","); - sb.append("maxBytesPerOutgoingDatagram"); + sb.append("maxBytesPerOutgoingDatagram:"); sb.append(mMaxBytesPerOutgoingDatagram); + sb.append(","); + + sb.append("antennaPositionMap:"); + sb.append(mAntennaPositionMap); return sb.toString(); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SatelliteCapabilities that = (SatelliteCapabilities) o; + return Objects.equals(mSupportedRadioTechnologies, that.mSupportedRadioTechnologies) + && mIsPointingRequired == that.mIsPointingRequired + && mMaxBytesPerOutgoingDatagram == that.mMaxBytesPerOutgoingDatagram + && Objects.equals(mAntennaPositionMap, that.mAntennaPositionMap); + } + + @Override + public int hashCode() { + return Objects.hash(mSupportedRadioTechnologies, mIsPointingRequired, + mMaxBytesPerOutgoingDatagram, mAntennaPositionMap); + } + /** * @return The list of technologies supported by the satellite modem. */ @@ -141,6 +186,16 @@ public final class SatelliteCapabilities implements Parcelable { return mMaxBytesPerOutgoingDatagram; } + /** + * Antenna Position received from satellite modem which gives information about antenna + * direction to be used with satellite communication and suggested device hold positions. + * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition + */ + @NonNull + public Map<Integer, AntennaPosition> getAntennaPositionMap() { + return mAntennaPositionMap; + } + private void readFromParcel(Parcel in) { mSupportedRadioTechnologies = new HashSet<>(); int numSupportedRadioTechnologies = in.readInt(); @@ -152,5 +207,14 @@ public final class SatelliteCapabilities implements Parcelable { mIsPointingRequired = in.readBoolean(); mMaxBytesPerOutgoingDatagram = in.readInt(); + + mAntennaPositionMap = new HashMap<>(); + int antennaPositionMapSize = in.readInt(); + for (int i = 0; i < antennaPositionMapSize; i++) { + int key = in.readInt(); + AntennaPosition antennaPosition = in.readParcelable( + AntennaPosition.class.getClassLoader(), AntennaPosition.class); + mAntennaPositionMap.put(key, antennaPosition); + } } } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 20f9bc8bef05..5681ab266c17 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -334,6 +334,46 @@ public class SatelliteManager { @Retention(RetentionPolicy.SOURCE) public @interface NTRadioTechnology {} + /** Suggested device hold position is unknown. */ + public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; + /** User is suggested to hold the device in portrait mode. */ + public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; + /** User is suggested to hold the device in landscape mode with left hand. */ + public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; + /** User is suggested to hold the device in landscape mode with right hand. */ + public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; + + /** @hide */ + @IntDef(prefix = {"DEVICE_HOLD_POSITION_"}, value = { + DEVICE_HOLD_POSITION_UNKNOWN, + DEVICE_HOLD_POSITION_PORTRAIT, + DEVICE_HOLD_POSITION_LANDSCAPE_LEFT, + DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceHoldPosition {} + + /** Display mode is unknown. */ + public static final int DISPLAY_MODE_UNKNOWN = 0; + /** Display mode of the device used for satellite communication for non-foldable phones. */ + public static final int DISPLAY_MODE_FIXED = 1; + /** Display mode of the device used for satellite communication for foldabale phones when the + * device is opened. */ + public static final int DISPLAY_MODE_OPENED = 2; + /** Display mode of the device used for satellite communication for foldabable phones when the + * device is closed. */ + public static final int DISPLAY_MODE_CLOSED = 3; + + /** @hide */ + @IntDef(prefix = {"ANTENNA_POSITION_"}, value = { + DISPLAY_MODE_UNKNOWN, + DISPLAY_MODE_FIXED, + DISPLAY_MODE_OPENED, + DISPLAY_MODE_CLOSED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DisplayMode {} + /** * Request to enable or disable the satellite modem and demo mode. If the satellite modem is * enabled, this may also disable the cellular modem, and if the satellite modem is disabled, diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl index cd69da18c5b0..eaf96abeb80a 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl +++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl @@ -17,7 +17,7 @@ package android.telephony.satellite.stub; import android.telephony.satellite.stub.NTRadioTechnology; - +import android.telephony.satellite.AntennaPosition; /** * {@hide} */ @@ -36,4 +36,14 @@ parcelable SatelliteCapabilities { * The maximum number of bytes per datagram that can be sent over satellite. */ int maxBytesPerOutgoingDatagram; + + /** + * Keys which are used to fill mAntennaPositionMap. + */ + int[] antennaPositionKeys; + + /** + * Antenna Position for different display modes received from satellite modem. + */ + AntennaPosition[] antennaPositionValues; } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index cbdf38ae95d4..ee9d6c1a2448 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2989,4 +2989,14 @@ interface ITelephony { * {@code false} otherwise. */ boolean setSatelliteServicePackageName(in String servicePackageName); + + /** + * This API can be used by only CTS to update the timeout duration in milliseconds that + * satellite should stay at listening mode to wait for the next incoming page before disabling + * listening mode. + * + * @param timeoutMillis The timeout duration in millisecond. + * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise. + */ + boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis); } diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index f2ffc19f2a4e..7272abba897d 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -29,6 +29,7 @@ <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> <option name="teardown-command" value="settings delete system show_touches" /> <option name="teardown-command" value="settings delete system pointer_location" /> + <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index f8d885ae3faf..d7fa124623ce 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -417,9 +417,9 @@ public class PackageWatchdogTest { int failureReason, int mitigationCount) { if (versionedPackage.getVersionCode() == VERSION_CODE) { // Only rollback for specific versionCode - return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; } - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } }; @@ -442,13 +442,13 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyAllDifferentImpacts() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_NONE); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observerMid = new TestObserver(OBSERVER_NAME_3, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); TestObserver observerLow = new TestObserver(OBSERVER_NAME_4, - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); // Start observing for all impact observers watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), @@ -499,9 +499,9 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing for observerFirst and observerSecond with failure handling watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); @@ -517,7 +517,7 @@ public class PackageWatchdogTest { assertThat(observerSecond.mMitigatedPackages).isEmpty(); // After observerFirst handles failure, next action it has is high impact - observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH; + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -531,7 +531,7 @@ public class PackageWatchdogTest { assertThat(observerFirst.mMitigatedPackages).isEmpty(); // After observerSecond handles failure, it has no further actions - observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; + observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -545,7 +545,7 @@ public class PackageWatchdogTest { assertThat(observerSecond.mMitigatedPackages).isEmpty(); // After observerFirst handles failure, it too has no further actions - observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -566,9 +566,9 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyOneSameImpact() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); // Start observing for observer1 and observer2 with failure handling watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); @@ -592,11 +592,11 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer3 = new TestObserver(OBSERVER_NAME_3, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); // Start observing with explicit health checks for APP_A and APP_B respectively @@ -645,7 +645,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and APP_B controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C)); @@ -711,7 +711,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and // package observation duration == LONG_DURATION @@ -742,7 +742,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and // package observation duration == SHORT_DURATION / 2 @@ -818,7 +818,7 @@ public class PackageWatchdogTest { // Start observing with failure handling TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); // Notify of NetworkStack failure @@ -1073,9 +1073,9 @@ public class PackageWatchdogTest { public void testBootLoopMitigationDoneForLowestUserImpact() { PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1); - bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW); + bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); - bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); watchdog.registerHealthObserver(bootObserver1); watchdog.registerHealthObserver(bootObserver2); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { @@ -1446,7 +1446,7 @@ public class PackageWatchdogTest { TestObserver(String name) { mName = name; - mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; } TestObserver(String name, int impact) { diff --git a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml index d2653d0de0d4..f20dd424c617 100644 --- a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml +++ b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml @@ -18,6 +18,6 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <gradient android:startColor="#000000" - android:endColor="#181818" + android:endColor="#222222" android:angle="0"/> </shape>
\ No newline at end of file diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml index 2bfb04fdb765..1731f6be4bf2 100644 --- a/tests/testables/tests/AndroidManifest.xml +++ b/tests/testables/tests/AndroidManifest.xml @@ -21,7 +21,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> - <application android:debuggable="true"> + <application android:debuggable="true" android:testOnly="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml new file mode 100644 index 000000000000..6d2979423efa --- /dev/null +++ b/tests/testables/tests/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs Testable Tests."> + <option name="test-tag" value="TestablesTests" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="TestablesTests.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.testables"/> + </test> +</configuration> diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index a146466402f6..e2c161482857 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -49,6 +49,7 @@ enum { SDK_S = 31, SDK_S_V2 = 32, SDK_TIRAMISU = 33, + SDK_UPSIDE_DOWN_CAKE = 34, SDK_CUR_DEVELOPMENT = 10000, }; diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 40bcef787815..e47704ea2725 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -59,6 +59,7 @@ enum : ApiVersion { SDK_S = 31, SDK_S_V2 = 32, SDK_TIRAMISU = 33, + SDK_UPSIDE_DOWN_CAKE = 34, SDK_CUR_DEVELOPMENT = 10000, }; diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java index 25fbabce71ae..166fbddc2f56 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java @@ -163,11 +163,11 @@ public final class NetworkProviderInfo implements Parcelable { /** * Sets the displayed connection strength of the remote device to the internet. * - * @param connectionStrength Connection strength in range 0 to 3. + * @param connectionStrength Connection strength in range 0 to 4. * @return Returns the Builder object. */ @NonNull - public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) { + public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) { mConnectionStrength = connectionStrength; return this; } @@ -205,8 +205,8 @@ public final class NetworkProviderInfo implements Parcelable { if (batteryPercentage < 0 || batteryPercentage > 100) { throw new IllegalArgumentException("BatteryPercentage must be in range 0-100"); } - if (connectionStrength < 0 || connectionStrength > 3) { - throw new IllegalArgumentException("ConnectionStrength must be in range 0-3"); + if (connectionStrength < 0 || connectionStrength > 4) { + throw new IllegalArgumentException("ConnectionStrength must be in range 0-4"); } } @@ -265,9 +265,9 @@ public final class NetworkProviderInfo implements Parcelable { /** * Gets the displayed connection strength of the remote device to the internet. * - * @return Returns the connection strength in range 0 to 3. + * @return Returns the connection strength in range 0 to 4. */ - @IntRange(from = 0, to = 3) + @IntRange(from = 0, to = 4) public int getConnectionStrength() { return mConnectionStrength; } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index e5ef62b16dfd..feef0497c152 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -35,6 +35,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; +import android.text.TextUtils; import android.util.Log; import com.android.internal.R; @@ -173,10 +174,15 @@ public class SharedConnectivityManager { R.string.config_sharedConnectivityServicePackage); String serviceIntentAction = resources.getString( R.string.config_sharedConnectivityServiceIntentAction); + if (TextUtils.isEmpty(servicePackageName) || TextUtils.isEmpty(serviceIntentAction)) { + Log.e(TAG, "To support shared connectivity service on this device, the" + + " service's package name and intent action strings must not be empty"); + return null; + } return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction); } catch (Resources.NotFoundException e) { Log.e(TAG, "To support shared connectivity service on this device, the service's" - + " package name and intent action string must be defined"); + + " package name and intent action strings must be defined"); } return null; } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java index b585bd5cfd7b..a03a6c2fc673 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java @@ -105,6 +105,13 @@ public class SharedConnectivityManagerTest { } @Test + public void resourceStringsAreEmpty_createShouldReturnNull() { + when(mResources.getString(anyInt())).thenReturn(""); + + assertThat(SharedConnectivityManager.create(mContext)).isNull(); + } + + @Test public void bindingToServiceOnFirstCallbackRegistration() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.registerCallback(mExecutor, mClientCallback); |